連載「技術ブログの収益化準備」第3回

Astro に GA4 を導入する|Cloudflare Pages の本番ビルドだけタグを出す(CF_PAGES_BRANCH)

この回でやること

連載「技術ブログの収益化準備」の第3回です。広告やアフィリエイトの効果を見るには、まずアクセス数が分からないと始まりません。そこで、Astro で作ったこのサイトに Google アナリティクス 4(GA4)のタグを入れました。

方針は次の3つです。

  • タグは全ページ共通のレイアウトの <head> に入れる
  • 測定ID(G- から始まる ID)は src/consts.ts に書く。空ならタグを出さない
  • Cloudflare Pages で main ブランチをビルドしたときだけタグを出す

3つ目は、手元での確認やプレビュー環境の閲覧が、本番のアクセス数に混ざらないようにするためです。

サイトの構成は、Astro(静的出力)+ microCMS + Cloudflare Pages です。全体像は総集編にまとめています。

前回の記事:プライバシーポリシーと Amazon アソシエイトの表記

本番ビルドかどうかを見分ける:CF_PAGES_BRANCH

Cloudflare Pages は、ビルドのときにいくつかの環境変数を自動で渡してくれます。その1つが CF_PAGES_BRANCH で、デプロイ中のブランチ名が入ります。

公式ドキュメントでも、使い道の例として「ブランチによってビルドを変える(本番ではデバッグログを出さない、など)」が挙がっています。

環境変数

値

CF_PAGES_BRANCH

デプロイ中のブランチ名

参考:Build configuration | Cloudflare Pages docs

このサイトの本番ブランチは main です。CF_PAGES_BRANCH が main のときだけタグを出せば、次のビルドではタグが出なくなります。

  • 手元での npm run dev や npm run build(CF_PAGES_BRANCH が未設定)
  • main 以外のブランチを push したときのプレビューデプロイ

実装

1. astro:env で CF_PAGES_BRANCH を読めるようにする

このサイトでは、環境変数を Astro の astro:env で型付きで読み込んでいます。astro.config.mjs の env.schema に CF_PAGES_BRANCH を追加します。

astro.config.mjs
	env: {
		// 未設定のままビルドすると、ここで分かりやすいエラーになる
		schema: {
			MICROCMS_SERVICE_DOMAIN: envField.string({ context: 'server', access: 'secret' }),
			MICROCMS_API_KEY: envField.string({ context: 'server', access: 'secret' }),
			// Cloudflare Pages がビルド時に自動で渡すブランチ名(ローカルでは未設定)
			CF_PAGES_BRANCH: envField.string({ context: 'server', access: 'public', optional: true }),
		},
	},

ポイントは2つです。

  • 手元では未設定なので optional: true にする。これがないと、手元のビルドが「変数が足りない」エラーで止まる
  • ブランチ名は秘密の値ではないので access: 'public' にする

astro:env の使い方は、連載「Astro × microCMS でブログを作る」の第1回で詳しく紹介しています。

参考:Using environment variables | Astro Docs

2. 測定IDを定数にする

GA4 の管理画面でウェブのデータストリームを作ると、G- から始まる測定IDが発行されます。これを src/consts.ts に書きます。

src/consts.ts
/** Google アナリティクス 4 の測定ID(G-XXXXXXXXXX)。空ならタグを出さない */
export const GA_MEASUREMENT_ID = 'G-XXXXXXXXXX';

測定ID はタグとしてページの HTML にそのまま出る値なので、隠す必要はありません(後述)。

3. タグを出すコンポーネントを作る

GA4 のタグを出すコンポーネントです。条件を満たしたときだけ、2つの <script> を出力します。

src/components/GoogleAnalytics.astro
---
import { CF_PAGES_BRANCH } from 'astro:env/server';
import { GA_MEASUREMENT_ID } from '../consts';

// Cloudflare Pages で main をビルドしたときだけタグを出す(ローカルやプレビューの閲覧を計測しない)
const enabled = CF_PAGES_BRANCH === 'main' && GA_MEASUREMENT_ID !== '';
---

{
	enabled && (
		<>
			<script is:inline async src={`https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}`} />
			<script is:inline define:vars={{ id: GA_MEASUREMENT_ID }}>
				window.dataLayer = window.dataLayer || [];
				window.gtag = function () {
					window.dataLayer.push(arguments);
				};
				window.gtag('js', new Date());
				window.gtag('config', id);
			</script>
		</>
	)
}

中身は、Google が案内している Google タグ(gtag.js)の設置コードを Astro 用に書き換えたものです。Google のコードは次の形です。

<script async src="https://www.googletagmanager.com/gtag/js?id=TAG_ID"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'TAG_ID');
</script>

参考:Set up the Google tag with gtag.js | Google for Developers

書き換えたところを順に説明します。

is:inline:Astro に手を加えさせない

Astro は通常、<script> の中身を処理してバンドルします。外部のタグはそのまま HTML に出したいので、is:inline を付けて処理の対象から外しています。Astro のドキュメントでは、is:inline を付けると中身は処理・最適化・バンドルされないと説明されています。

define:vars:測定IDをスクリプトに渡す

define:vars を使うと、Astro 側の値(ここでは測定ID)を <script> の中で変数として使えます。ドキュメントによると、<script> に define:vars を付けると is:inline も暗黙に付きます。

参考:Template directives reference | Astro Docs

window.gtag に代入している理由

define:vars を付けたスクリプトは、ビルド後に関数で包まれて出力されます。本番サイトの HTML を見ると、次のようになっていました(改行は整形)。

<script>(function(){const id = "G-XXXXXXXXXX";
  window.dataLayer = window.dataLayer || [];
  window.gtag = function () {
    window.dataLayer.push(arguments);
  };
  window.gtag('js', new Date());
  window.gtag('config', id);
})();</script>

Google のコードのように function gtag(){...} と書くと、gtag はこの関数の中だけで有効になり、ページの他の場所から呼べなくなります。そのため、window.gtag に代入してグローバルに置いています。dataLayer も同じ理由で window.dataLayer と明示しています。

4. 共通レイアウトの head に入れる

全ページ共通のレイアウトでコンポーネントを読み込み、<head> の中に置きます。

src/layouts/BaseLayout.astro
import GoogleAnalytics from '../components/GoogleAnalytics.astro';
// …(省略)…
		<meta name="twitter:card" content={image ? 'summary_large_image' : 'summary'} />

		<GoogleAnalytics />
	</head>

Google の案内では、<head> の開始タグの直後に貼るとされています。このサイトでは、メタタグを並べた後の <head> の最後に置いています。

ハマったこと:測定IDを .env に入れても反映されなかった

最初は、測定IDを .env に書きました。ところが、Cloudflare Pages の本番サイトにはタグが出ませんでした。理由は2つありました。

  1. .env は .gitignore で Git の管理から外しているので、GitHub に push されない。GitHub のリポジトリからビルドする Cloudflare Pages には届かない
  2. そもそもコードは src/consts.ts の GA_MEASUREMENT_ID を読む作りで、.env の値は使っていなかった

測定IDを src/consts.ts に書いてコミットしたところ、反映されました。

測定ID はタグの URL にそのまま入るので、ページのソースを見れば誰でも分かる値です。

$ curl -s https://cyber-blow.com/ | grep -oE 'gtag/js\?id=[A-Z0-9-]+'
gtag/js?id=G-XXXXXXXXXX

隠す意味がないので、APIキーのように環境変数にせず、コードに直接書いています。環境変数で渡したい場合は、Cloudflare Pages の管理画面の環境変数に設定し、astro:env の schema にも宣言する必要があります(このサイトの microCMS の APIキーはその方法です)。

注意点

pages.dev の URL でもタグが出る

Cloudflare Pages の本番デプロイは、カスタムドメインのほかに 〜.pages.dev の URL でも見られます。どちらも main のビルドなので、pages.dev の URL にもタグが出ています。

$ curl -s https://<PROJECT>.pages.dev/ | grep -oE 'gtag/js\?id=[A-Z0-9-]+'
gtag/js?id=G-XXXXXXXXXX

pages.dev 経由のアクセスも計測に入る点は、覚えておく必要があります。

タグが動いているかの確認

Google のドキュメントでは、ブラウザの開発者ツールで googletagmanager.com へのリクエストが出ていれば、タグが動作していると案内されています。本番サイトを開いて確認できます。

プライバシーポリシーへの記載

Google アナリティクスの利用規約では、プライバシーポリシーで Cookie の使用や Google アナリティクスの利用について開示することが求められています。このサイトの対応は前回の記事で紹介しています。

まとめ

  • Cloudflare Pages はビルド時に CF_PAGES_BRANCH(デプロイ中のブランチ名)を渡してくれる
  • astro:env に optional: true で宣言し、main のときだけ GA4 のタグを出した。手元のビルドやプレビューではタグが出ない
  • define:vars を付けたスクリプトは関数で包まれるので、gtag は window.gtag に代入する
  • 測定IDは公開される値なので、src/consts.ts に直接書いた。.env は Git に含めないので、Cloudflare のビルドには届かない

アドセンスの審査やアフィリエイトについては、実際に進めたらこの連載に回を追加していきます。