Astro ブログに GTM + GA4 を 30 分で導入する全手順|Cookie 同意バナー・アフィリクリック計測込み


本記事は実体験の記録です。アフィリエイトリンクは含みません。

この記事でわかること
  • なぜ GA4 を直接ではなく Google Tag Manager(GTM)経由で入れるのか
  • Astro の <head> に GTM スニペットを埋め込む実装
  • Cookie 同意バナー + Consent Mode v2 のシンプルな実装
  • アフィリエイトリンククリックを GA4 に送るカスタムイベントの仕込み

結論:GTM 経由が結果的に一番楽だった

副業ブログを立ち上げるにあたり、最初は「GA4 のスニペットを直接 Astro に貼ればいい」と思っていました。でも実際にやってみると、後から 広告クリック計測 / コンバージョン / Hotjar / Cookie 同意 といった追加要素を入れるたびにコードを触る羽目になります。

夫(エンジニア)と相談した結果、最初から Google Tag Manager(GTM)を入口にする構成にしました。GTM だけ Astro に埋め込めば、その先に GA4・カスタムイベント・将来追加するタグを Web 管理画面から差し込めます。サイトのコードを再ビルドしなくて済むのが大きい。

採用した構成(3 行)
  • GTM(GTM-XXXXXXX)を Astro <BaseHead> で配信
  • Consent Mode v2 でデフォルト denied、Cookie 同意で update
  • アフィリクリックは外部リンクの click を dataLayer.push → GTM で GA4 イベントに変換

1. Astro の <BaseHead> に GTM スニペットを入れる

Astro 公式 blog テンプレでは、src/components/BaseHead.astro に共通の <head> 要素がまとまっています。ここに GTM スニペットを追加します。

ポイントは 2 つ:

  • 環境変数で GTM ID を切り替える:開発時はオフ、本番だけオンにできるようにする
  • Consent Mode v2 を gtm.js ロード前に設定:デフォルト denied で読み込んで、同意後に granted に切り替える

実際のコードはおおよそ次のようになります(簡略版)。

{import.meta.env.PUBLIC_GTM_ID && (
  <script is:inline define:vars={{ gtmId: import.meta.env.PUBLIC_GTM_ID }}>
    window.dataLayer = window.dataLayer || [];
    function gtag() { window.dataLayer.push(arguments); }
    window.gtag = gtag;

    // デフォルトはすべて denied
    gtag('consent', 'default', {
      analytics_storage: 'denied',
      ad_storage: 'denied',
      ad_user_data: 'denied',
      ad_personalization: 'denied',
      functionality_storage: 'granted',
      security_storage: 'granted',
    });

    // 過去に同意済みなら update
    try {
      if (localStorage.getItem('cookie_consent') === 'accepted') {
        gtag('consent', 'update', {
          analytics_storage: 'granted',
          ad_storage: 'granted',
          ad_user_data: 'granted',
          ad_personalization: 'granted',
        });
      }
    } catch (e) {}

    // GTM 本体
    (function (w, d, s, l, i) {
      w[l] = w[l] || [];
      w[l].push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
      var f = d.getElementsByTagName(s)[0],
        j = d.createElement(s),
        dl = l != 'dataLayer' ? '&l=' + l : '';
      j.async = true;
      j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl;
      f.parentNode.insertBefore(j, f);
    })(window, document, 'script', 'dataLayer', gtmId);
  </script>
)}

<noscript> の iframe は Header コンポーネントの <body> 直後に配置するか、Astro の場合は Header の中で出すと収まりが良いです。

.env に以下を書いて、本番ビルドだけ GTM が読まれる状態にしました。

PUBLIC_GTM_ID=GTM-XXXXXXX

Cookie 同意バナーは独立コンポーネントにしました。やることは少なくて、

  1. localStorage に cookie_consent の値があるか確認
  2. なければバナー表示
  3. 「同意する」ボタンで gtag('consent', 'update', {...granted...}) を呼ぶ
  4. 「拒否する」ボタンで denied を維持
  5. localStorage に保存して、次回以降はバナー非表示
function setConsent(value) {
  localStorage.setItem('cookie_consent', value);
  banner.classList.add('hidden');
  if (value === 'accepted' && typeof window.gtag === 'function') {
    window.gtag('consent', 'update', {
      analytics_storage: 'granted',
      ad_storage: 'granted',
      ad_user_data: 'granted',
      ad_personalization: 'granted',
    });
  }
}

GDPR 規制対応の細かい要件(拒否ボタンの表示位置、再同意の頻度など)は厳密にやり始めるとキリがないので、個人ブログのレベルでは「同意 / 拒否ボタンが両方出る」「拒否を選んだら計測が止まる」が満たせれば十分と判断しました。

3. アフィリクリック計測

外部リンクのクリックを GA4 にカスタムイベントとして送る仕組みを、Footer に小さなスクリプトで仕込みました。

document.addEventListener('click', function (e) {
  const link = e.target && e.target.closest && e.target.closest('a');
  if (!link || !link.href) return;
  const isExternal = link.host && link.host !== location.host;
  if (!isExternal) return;
  const isAffiliate =
    link.dataset.aff === '1' ||
    /(a8\.net|moshimo|valuecommerce|afi-b\.com|accesstrade|impact)/i.test(link.href);
  if (!isAffiliate) return;

  window.dataLayer = window.dataLayer || [];
  window.dataLayer.push({
    event: 'aff_click',
    affiliate_url: link.href,
    affiliate_anchor: (link.textContent || '').trim().slice(0, 100),
    affiliate_program: link.dataset.affProgram || '',
    page_path: location.pathname,
  });
});

ASP の代表ドメイン(A8.net、もしも、バリュコマ、afi-b、アクセストレード、Impact)を正規表現で判定。マッチしたリンククリックで aff_click という dataLayer イベントを発火します。

GTM 側で「カスタムイベント aff_click のトリガー」と「GA4 イベント affiliate_click のタグ」をペアで作って、Web 管理画面側で計測ロジックを完結させました。

4. GTM での設定

サイト側の仕込みが終わったら、GTM 管理画面で以下を順に作ります。

4-1. Google tag

すべての基盤になるタグ。

  • タグタイプ: Google tag
  • Tag ID: G-XXXXXXXXXX(GA4 の測定 ID)
  • トリガー: Initialization - All Pages

Initialization トリガーは GTM の中でも最早期に発火するので、Consent Mode と相性が良いです。

4-2. データレイヤー変数

aff_click イベントで送られるデータを GTM 側で受け取るための変数を 2 つ作ります。

  • dlv - affiliate_url (データレイヤー変数名: affiliate_url
  • dlv - affiliate_program (データレイヤー変数名: affiliate_program

4-3. カスタムイベント トリガー

  • トリガータイプ: カスタム イベント
  • イベント名: aff_click
  • このトリガーの発生場所: すべてのカスタム イベント
  • 名前: Custom - aff_click

4-4. GA4 イベント タグ

  • タグタイプ: Google アナリティクス: GA4 イベント
  • 測定 ID: 上の Google tag を参照(または直接入力)
  • イベント名: affiliate_click
  • パラメータ: affiliate_url / affiliate_program / link_url{{Click URL}} 組み込み変数)
  • トリガー: Custom - aff_click

最後に右上の「公開(Publish)」を押せば、新しいバージョンとして全サイトに反映されます。

つまずいたところ

「No Google tag found in this container」警告

最初に GTM で 「Google アナリティクス: GA4 Event」 を選んでしまい、警告が出ました。GA4 Event タグは「すでに Google tag が設定されているコンテナ」での追加イベント送信用なので、まず Google tag を作って公開しないと動きません

正しい順序は Google tag → 公開 → GA4 Event です。

Initialization と All Pages の混在

Google tag のトリガーに Initialization - All PagesAll Pages の両方を設定していて、重複発火しそうな状態になりました。Google tag は Initialization のみが推奨で、All Pages トリガーはカスタムイベント側で使う想定。

動作確認

GA4 のリアルタイムレポートで、自分のセッションが見えれば計測オン成功です。確認時は シークレットウィンドウ + Cookie 同意「同意する」 がポイント。同意していないと Consent Mode が denied のままで GA4 にデータが送られません。

アフィリクリック計測の方は、ASP 提携承認 → 実 URL に置換 → 自分でクリックして affiliate_click イベントが発火するかを確認する流れになります。

まとめ

この構成の利点
  • 計測タグを増やすたびに Astro を再ビルドしなくて良い
  • Cookie 同意の状態管理を Consent Mode に統一できる
  • アフィリクリックを案件単位で追跡できる(特単交渉の根拠にも)
  • 個人ブログから将来的に拡張しやすい

GTM・GA4・Cookie 同意バナーまで含めても、Astro 側の作業は 半日もかからず完了しました。GTM 管理画面の操作にやや慣れが要りますが、一度通すと「次のサイトでも同じ構成を使えそう」という再現性が出てきます。

このブログでも引き続き、副業立ち上げの実体験を記録していきます。次回はアフィリエイト ASP(A8 や もしも)への申請まわりを書く予定です。