AstroのGA4にlocalhostやpages.devのアクセスが混ざる問題を、hostnameガードで防ぐ


AstroでGA4のタグを入れるとき、import.meta.env.PROD で本番ビルドのときだけ読み込む、という書き方がよく紹介されている。この書き方だけだと、本番以外のURLからもGA4にPVが送られる。自分のサイトで実際に起きたことと、直した方法をまとめる。

症状:BigQueryに、127.0.0.1 と pages.dev のPVが入っていた

BigQueryに出力したGA4のデータを、page_location ごとに数えてみた。全309PVの内訳は次の通りだった。

page_locationの種類 PV
本番ドメイン(getcclab.com / www) 280
ローカル(127.0.0.1) 6
Cloudflare Pagesの確認用URL(pages.dev) 23

ローカルが6、pages.dev が23で、合わせて約1割がサイト運営側のノイズだった。

原因:PROD は「本番のURL」を意味しない

import.meta.env.PROD は、astro build で作ったビルドかどうかを示すだけで、どのドメインで表示されているかは見ていない。次の2つの場合に、タグが発火してしまう。

  • astro build のあとに astro preview で確認すると、127.0.0.1 で配信されるが、ビルドは本番用なのでタグが読み込まれる
  • Cloudflare Pagesは、デプロイごとに確認用のURL(〇〇.pages.dev)でも同じ内容を配信する。そこにアクセスしても、タグが読み込まれる

対処:ホスト名を見てから読み込む

タグの読み込みを、ホスト名が本番ドメインのときだけにした。

{import.meta.env.PROD && (
  <script is:inline>
    (function () {
      var h = location.hostname;
      if (h !== 'getcclab.com' && h !== 'www.getcclab.com') return;
      var s = document.createElement('script');
      s.async = true;
      s.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX';
      document.head.appendChild(s);
      window.dataLayer = window.dataLayer || [];
      window.gtag = function () { dataLayer.push(arguments); };
      gtag('js', new Date());
      gtag('config', 'G-XXXXXXXXXX');
    })();
  </script>
)}

ポイントは、gtag.js 自体も、ホスト名が合ったときにだけ読み込むようにしたところ。config だけ条件にしても、スクリプトは読み込まれる。

動作確認

デプロイ後、ブラウザで本番と pages.dev を開いて、ページ内の状態を調べた。

URL gtag.js のscriptタグ dataLayer の件数
getcclab.com あり 4
pages.dev なし 0

本番では計測が動き、pages.dev では何も送られていないことを確認できた。

過去データは、SQLで除外する

すでにBigQueryに入っている過去分は消せないので、集計のときに除外する。

SELECT COUNT(*) AS pv
FROM `プロジェクトID.analytics_XXXXXXXXX.events_*`
WHERE event_name = "page_view"
  AND REGEXP_CONTAINS(
    (SELECT value.string_value FROM UNNEST(event_params) WHERE key = "page_location"),
    r"^https://(www\.)?getcclab\.com/"
  )

この条件で数えると、上の表の「本番ドメイン」の280が返ってくる。

残っている課題

この方法で防げるのは、ホスト名が違うアクセスだけだ。本番のURLを自分で見たアクセスは、これでは除外できない。そちらはGA4側のIPアドレスによる除外か、ブラウザの拡張機能での対処になる。