Claude Codeで計測基盤(GSC/GA4→BigQuery)を自動化した
前回、このサイトをAstro + Cloudflare Pagesで構築した話を書いた。今回はその続きで、計測基盤を作った。
なぜ記事ゼロの段階で計測基盤を先に作るのか
普通、ブログは記事を書いてから「アクセス解析でも入れるか」となる。今回はあえて逆にした。理由は単純で、後から作ると詰むからだ。
Google Search Console はデータを16ヶ月しか遡れない。記事を書き始めてから3ヶ月後に「そういえば計測してなかった」と気づいても、その3ヶ月分のデータは永久に戻ってこない。積み上げ型のコンテンツほど、初速のデータほど後から価値が上がる。
もう一つの理由は、Claude Code に運用を任せる前提で作っているなら、判断材料になるデータがない状態で自動化しても意味がないということだ。「どの記事をリライトすべきか」「どのキーワードで流入しているか」を自動で拾わせるパイプラインを作るには、まずパイプラインの受け皿(BigQuery)とデータの出口(GSC/GA4)を先に用意しておく必要がある。
正直に言うと、ここまで丁寧に計測基盤を組む個人ブログはあまり見ない。手間のわりに即効性がないからだ。それでもやる理由は、また別の機会に書く。
やったこと
1. GA4プロパティの作成
analytics.google.com でプロパティを新規作成した。プロパティ名は ccLab、タイムゾーンと通貨は日本に設定。業種は「インターネット、通信事業」、規模は「小規模」を選択した。ビジネス目標は「ウェブ/アプリのトラフィックの分析」だけに絞った。EC機能もリード獲得機能も今のところ要らない。
作成後にウェブストリームを追加すると測定ID(G- から始まるID)が発行される。これを Astro の BaseHead.astro に埋め込んだ。
{import.meta.env.PROD && (
<>
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script is:inline>
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script>
</>
)}
import.meta.env.PROD で本番ビルド時のみ読み込むようにしている。ローカル開発中にダミーのアクセスがGA4に記録されるのを防ぐためだ。地味だが、これを忘れると自分のローカル確認アクセスが本番データに混ざり続けることになる。
2. GA4 → BigQuery のネイティブ連携
ここが一番の収穫だった。GA4の管理画面には「BigQueryのリンク」という機能があり、Cloud Functionを一切書かずにイベントデータを日次でBigQueryにエクスポートできる。Googleの公式機能で、無料。
最初は「GA4のデータもSearch Console同様、自前でAPIを叩いてCloud Functionで取り込むしかないだろう」と思って着手した。実際に手を動かし始めてから、この管理画面の機能に気づいた。もし気づかずに突き進んでいたら、Measurement Protocolまわりの認証、Cloud Schedulerでのジョブ管理、エラー時のリトライ処理まで自分で書くところだった。
設定は管理画面から数クリックで完了する。
- リンク先:既存のGCPプロジェクト(朝メール送信で使っているものと共用。プロジェクトを増やすと管理対象が増えるだけなので、既存資産に相乗りする方針にした)
- データのロケーション:東京 (asia-northeast1)。BigQueryのデータセット(
affiliate)も同じリージョンに揃え、リージョンをまたぐクエリコストが発生しないようにした - エクスポートタイプ:毎日(ストリーミングも選べるが、今回のアクセス規模ではリアルタイム性が要らないので不要と判断)
- ユーザーデータのエクスポートは無効のまま(個人を特定しうる範囲を最小化する狙い)
エクスポートされたデータは events_YYYYMMDD という命名のテーブルとしてBigQueryに日次で積まれていく。SQLで直接クエリできるようになるので、GA4の管理画面のUIでは追いにくい複雑な条件(「特定の流入経路かつ特定のページ滞在時間以上」のような掛け合わせ)も自由に書けるようになる。これが週次のリライト候補抽出などを自動化する際の土台になる。
3. Search Console の所有権確認
ドメインプロパティとして getcclab.com を登録した。サブドメイン・プロトコル問わず全部カバーできるのがドメインプロパティの利点で、www.getcclab.com と getcclab.com を別々に管理しなくて済む。
DNSの所有権確認は、Cloudflareを使っている場合はワンクリックで終わる。Search Console側が「Cloudflare.com」を自動検出し、Domain Connect という仕組みでTXTレコードを直接追加してくれる。手動でDNS管理画面を開いてレコードをコピペする必要がない。
Google が getcclab.com に以下の Cloudflare DNS レコードを追加します。
TXT | getcclab.com | "google-site-verification=..."
これは一度限りの承認です。Google が今後変更を行う権限を与えるものではありません。
この一文の通り、権限は該当レコード1件の追加だけに限定される。承認して数秒で「所有権を証明しました」と表示された。DNS伝播を待つ必要すらなかった。
4. サイトマップを送っておかしいことに気づいた
最後にサイトマップを送信しようとして、sitemap-index.xml の中身を確認したら異変に気づいた。
<sitemapindex ...><sitemap><loc>https://example.com/sitemap-0.xml</loc></sitemap></sitemapindex>
getcclab.com ではなく example.com になっていた。原因は astro.config.mjs の site 設定を、サイト公開時に更新し忘れていたこと。
// 修正前
site: 'https://example.com',
// 修正後
site: 'https://getcclab.com',
たった1行だが、影響範囲は広い。サイトマップだけでなく、canonical URL・OGPのURL・RSSフィードのリンクまで、公開初日からずっと example.com を指していたことになる。
curl -sS https://getcclab.com/ | grep canonical
<link rel="canonical" href="https://getcclab.com/">
修正後にこうやって1つずつ叩いて確認した。幸い検索エンジンに拾われる前に気づけたが、記事を3週間書き進めてから発覚していたら、かなり気まずいことになっていた。
計測基盤を先に作っておいてよかった、と思った瞬間の一つ。データを見ようとしなければ、このバグにはもっと後まで気づかなかった。
残っているタスク
GA4は前述の通りネイティブ連携で解決したが、Search Console には同等の仕組みがない。BigQueryへの日次取り込みだけは、Search Console APIを叩く自前のパイプラインが必要になる。ここは記事がある程度溜まってデータが意味を持ち始めてから着手する予定。
次は、このサイトの運用をどこまでAIに任せて、どこから自分の手を動かしているか、その線引きの話を書く。