ブログ一覧

Google Analytics を入れずにブログ群を運用する — Cloudflare GraphQL Analytics と GitHub Actions による監視設計

アクセス解析を入れるとなると、まず GA4(Google Analytics 4)の測定タグを貼る、というのが多くの現場の初期設定です。ですが、複数のブログを運用していて「まず知りたいのは、ちゃんと配信できているか・壊れていないか」だとすると、GA4 だけでは足りず、そして GA4 でなくても取れる情報がかなりあります。

このサイトを含む複数のブログは、GA4(gtag.js)を一切入れていません。代わりに、Cloudflare のエッジ計測(GraphQL Analytics API)と Google Search Console、それを回す GitHub Actions の 3 つで運用の可視化をまかなっています。

本記事は「GA を捨てろ」という話ではありません。アクセス解析には「クライアント側で計る層」と「エッジ(配信側)で計る層」という別レイヤーがあり、用途によっては後者だけで運用が回る、という整理です。実際に動かしている構成と、GA との役割分担、ハマりどころをまとめます。

前提: サイトを Cloudflare(Workers / Pages)で配信していることが条件です。エッジ計測は Cloudflare がリクエストを受けている前提で成り立ちます。

GA4 とエッジ計測は「測っている場所」が違う

同じ「アクセス解析」でも、GA4 とエッジ計測は測定点がまったく違います。ここを混ぜると議論が噛み合いません。

GA4(クライアント側 JS 計測) は、ブラウザで gtag.js を実行して初めてデータが飛びます。だから取れるものと取れないものがはっきりしています。

  • 取れる: ページ内のスクロールやクリック、滞在時間、コンバージョン、参照元、擬似的なユーザー単位の行動
  • 取れない / 弱い: JS を実行しないボット、スクリプトブロッカーやトラッキング拒否の環境(そのぶん欠測する)
  • コスト: スクリプトの読み込み・実行がフロントに乗る(パフォーマンス面の負荷)
  • 論点: Cookie や識別子を使うため、同意バナーや外部送信規律への対応を検討する必要が出てくる(EU の GDPR/ePrivacy、日本では改正電気通信事業法の外部送信規律が 2023 年 6 月に施行。詳しくは後述の一次情報を参照)

エッジ計測(Cloudflare) は、Cloudflare のエッジに届いた全リクエストをサーバー側で記録します。クライアントには何も追加しません。

  • 取れる: ボット込みの生トラフィック、ステータスコード別の件数(404 や 5xx などの障害系がそのまま見える)、ホスト別・パス別の内訳
  • 取れない: ページ内の行動、滞在時間、コンバージョン、「人間の読者が何人か」の絶対値
  • コスト: クライアントに一切スクリプトを足さない(Cookie レス。同意バナーの論点がそもそも発生しない)

この 2 つは代替関係ではなく、見ている面が直交しています。「読者がどう読んだか」を知りたいなら GA4、「配信が壊れていないか・どこに穴が空いているか」を知りたいならエッジ計測、という住み分けです。運用の一次監視として欲しいのは後者だった、というのがこの構成の出発点です。

手順1: Cloudflare GraphQL Analytics で「配信の生データ」を引く

Cloudflare は GraphQL Analytics API を提供しています。エンドポイントは 1 つ(https://api.cloudflare.com/client/v4/graphql)で、ここに POST でクエリを投げます。

アカウント全体の HTTP トラフィックは httpRequestsAdaptiveGroupsデータセット一覧)で取れます。ホスト別・ステータス別に絞り込めるので、複数サイトを 1 回のクエリで横断できます。

query ($acc: string, $since: Time, $until: Time) {
  viewer {
    accounts(filter: { accountTag: $acc }) {
      notFound: httpRequestsAdaptiveGroups(
        filter: {
          datetime_geq: $since, datetime_lt: $until,
          edgeResponseStatus: 404, requestSource: "eyeball"
        }
        limit: 500, orderBy: [count_DESC]
      ) {
        count
        dimensions { clientRequestHTTPHost clientRequestPath }
      }
    }
  }
}

ポイントは 2 つあります。

  • `requestSource: "eyeball"`: 「エッジに実際に届いたリクエスト」に限定するフィルタです。これを付けないと内部的なリクエストも混ざります。
  • `edgeResponseStatus`: 404 指定や edgeResponseStatus_geq: 500(5xx 以上)でステータスを直接絞れます。これが GA4 では取りづらい、「エラーを返したリクエスト」そのものを数えるという視点です。

このクエリを Node の標準 fetch で叩き、ホスト別に requests / 404 / 5xx を集計、前期比(前週比)を添えて週次レポートにしています。5xx が出ているパス、そしてスキャナのノイズを除いた「対処すべき本物の 404」の上位を並べる、というのが実運用の中身です。

curl -s https://api.cloudflare.com/client/v4/graphql \
  -H "Authorization: Bearer $CLOUDFLARE_ANALYTICS_TOKEN" \
  -H "Content-Type: application/json" \
  --data @query.json

無料プランの場合、httpRequestsAdaptiveGroups保持期間はおよそ 30 日です。長期のトレンドを追う用途には向きませんが、「直近で壊れていないか」の定点観測には十分です。長期のトレンドは Search Console 側で見ます(後述)。

手順2: 読み取り専用トークンを分離する

このレポートに必要な権限は読み取りだけです。デプロイ用の万能トークンを使い回すべきではありません。Cloudflare の API トークンを、監視専用に別発行します。

  • Account Analytics: Read(GraphQL Analytics を読むのに必要)
  • Account Settings: Read(トークンからアカウント ID を引くのに必要)

この 2 つだけを持つトークンを作り、CLOUDFLARE_ANALYTICS_TOKEN として監視ジョブに渡します。デプロイ用の CLOUDFLARE_API_TOKEN(書き込み権限を持つ)とは別物として分けるのが定石です。監視のために漏れると困るトークンの権限範囲を、最初から最小にしておきます。

手順3: 監視者を「被監視系の外」に置く — GitHub Actions

ここが設計上いちばん大事なところです。Cloudflare を監視する仕組みを、Cloudflare の中に置いてはいけません。

Cloudflare Workers の Cron Triggers だけで死活監視を組むと、Cloudflare 自体が落ちたときに監視も一緒に沈黙します。監視者は、被監視系とは別の障害ドメインに置く必要があります。ここでは GitHub Actions の schedule(cron)を監視者にしています。「Cloudflare が落ちたら GitHub 側のジョブが失敗して気づく」という、障害ドメインの分離です。

実際のジョブは 2 系統です。

  • 死活チェック(1 日 2 回): 全サイトのトップページを GET し、最終ステータス 200 を確認する。一時的なネットワーク断でのフラッピングを避けるため、失敗分だけ 30 秒待って 1 回リトライする。失敗したときだけ Slack に通知し、ジョブを異常終了させる。
  • 週次レポート(週 1 回): 手順 1 のクエリを回し、ホスト別トラフィックと 404 / 5xx を Slack に投稿する。さらに対処候補(5xx か本物の 404)があった週だけ、GitHub Issue を自動起票する。
on:
  schedule:
    - cron: "0 0,12 * * *"  # JST 9:00 / 21:00 → 死活チェック
    - cron: "0 23 * * 0"    # 月曜 JST 8:00 → 週次レポート

この Issue 起票がキモで、検出されたエラーをそのまま「作業チケット」に変換しています。起票された Issue を、後日セッションで「これ対処しといて」と AI エージェントに渡す運用です。監視が「Slack を見て人が気づく」で止まらず、対処待ちキューとして積まれるので、取りこぼしにくくなります。

実際にこのレポートで見つかったもの

このエッジ計測は、GA4 なら埋もれていたであろう障害を実際に掘り出しています。

  • 全サイトで sitemap が 500 を返していた。5xx フィルタが /sitemap.xml を上位に出して発覚。GA4 は「読者が来なかった」ことは示しても、「クローラが 500 で弾かれていた」ことは教えてくれません。
  • あるサイトで 404 が週あたり +3,743 件に急増。前週比の差分が異常値として立ち、パス一覧から原因を特定できました。
  • 記事内の画像が 404。本文からリンクされた画像パスが 404 上位に並び、リンク切れとして見つかりました。

いずれも「配信側のステータスコード」を数えているから見えるもので、クライアント側の行動計測(GA4)とは検出できる障害の種類が違う、という具体例です。

エラーに見えて正常・逆にハマる点

/404 への 302 が 404 件数の大半を占める

各サイトは存在しない URL を /404 に 302 リダイレクトする実装になっています。すると Cloudflare の 404 集計には、`/404` ページ自体へのアクセスが大量に混ざります。これはリダイレクト先なので「対処すべき 404」ではありません。集計時に /404 を別枠に切り出し、ノイズ(スキャナが総当たりする wp-login.env.git などのパス)も正規表現で除外して初めて、「本当に直すべき 404」が見えます。生の 404 件数をそのまま眺めても意味がありません。

なお、この「/404」への 302 方式そのものをやめ、Astro.rewrite で元 URL を保ったまま正しく 404 を返す設計は「Astro SSR で存在しない URL に正しく 404 を返す」にまとめています。

Bot Fight Mode が監視リクエスト自体を弾く

Cloudflare の Bot Fight Mode を有効にしていると、GitHub Actions からの死活チェックが 403 で弾かれることがあります。監視リクエストは「データセンター IP + 非ブラウザ UA」という、ボットそっくりの特徴を持つためです。死活チェックが謎の 403 を出し続けるときは、まず監視元 IP / UA が Bot Fight Mode に引っかかっていないかを疑ってください。

検索流入の分析は Search Console が担う

エッジ計測に無いのは「どんな検索クエリで、何位で、どれだけ入ってきたか」です。ここは GA ではなく Google Search Console が担当します。GA4 の代替は「Cloudflare エッジ計測 + Search Console」の組み合わせだ、という理解が正確です。

Search Console API をスクリプトから叩いて定点レポート化する話は、別記事にまとめています。2026 年時点ではサービスアカウント追加が Google 側バグで詰まるので、回避策も含めて参照してください: 「サービスアカウントが Search Console に追加できない(email not found)— Google 側バグと gcloud ADC での回避」。

この構成の割り切り

GA なしで成立する用途と、しない用途を正直に書いておきます。

  • 成立する: 「配信が壊れていないか」「どのページが 404/5xx になっているか」「ホスト別にどれくらいリクエストが来ているか」「検索でどう拾われているか」を知りたい運用。ブログ群の健全性監視と SEO 観測は、この構成で回ります。
  • 成立しない / GA を足すべき: ページ内の行動導線、滞在時間、コンバージョン率、ファネル分析、A/B テストの効果測定。「人間の読者がどう振る舞ったか」を測りたいなら GA4(や同種のクライアント計測)が要ります。エッジ計測は「人間の読者数の絶対値」にはなりません(ボット込みの生トラフィックだからです)。

「まず配信の健全性、必要になったら行動計測を足す」という順番なら、初期は GA を入れずにエッジ計測 + Search Console で始めて問題ありません。同意バナーや外部送信規律の対応コストを、必要になるまで先送りできるのも利点です。

まとめ

  • アクセス解析にはクライアント計測(GA4)とエッジ計測(Cloudflare)という別レイヤーがある。前者は行動・滞在・CV、後者は生トラフィックと 404/5xx が見える
  • Cloudflare の httpRequestsAdaptiveGroupsrequestSource: "eyeball")を GraphQL で叩けば、クライアントにスクリプトを足さずに配信の生データが取れる。無料プランの保持は約 30 日
  • トークンは監視専用に読み取り権限だけ(Account Analytics: Read + Account Settings: Read)で別発行する
  • 監視者は被監視系の外(GitHub Actions)に置く。Workers Cron だけだと Cloudflare ごと落ちたとき沈黙する
  • 検索流入は Search Console が担う。GA4 の代替は「エッジ計測 + Search Console」の組み合わせ
  • ページ内行動・滞在・CV を測りたくなったら、そこで初めて GA4 を足せばよい

参考リンク