Skip to content
Read Uncommitted
Go back

Astro + Partytown で GA4 の計測が動かなかった話

TL;DR

はじめに

このブログは Astro v6(AstroPaper v5 テンプレート)で作られていて、Cloudflare Workers の静的アセット配信機能で動いています。せっかくならアクセス解析を入れようと思い、Google Analytics 4 (GA4) の導入に取り組みました。

パフォーマンス最適化を重視して、最初は @astrojs/partytown を使って gtag.js を Web Worker にオフロードする方針を取りました。メインスレッドから重たいトラッキングコードを追い出せるため、Lighthouse スコアにも優しい構成です。

しかし、デプロイしても GA4 のリアルタイムレポートに一切アクセスが計上されないという問題に直面しました。

症状

本番環境(https://read-uncommitted.com/)で以下を確認しました。

スクリプトはロードされている、Service Worker も動いている、なのにデータが飛ばない。見た目上は「動いているはず」なのに計測が走らないという、切り分けが難しい症状でした。

実装していた内容

以下が最初の実装です(抜粋)。

astro.config.ts:

integrations: [
  sitemap({ ... }),
  partytown({
    config: {
      forward: ["dataLayer.push"],
    },
  }),
],

src/layouts/Layout.astroPUBLIC_GA_ID がセットされていれば埋め込む):

<!-- main thread: gtag スタブ + astro:page-load リスナー -->
<script is:inline>
  window.dataLayer = window.dataLayer || [];
  window.gtag = function () {
    window.dataLayer.push(arguments);
  };
  document.addEventListener("astro:page-load", function () {
    window.gtag("event", "page_view", {
      page_path: location.pathname + location.search,
      page_title: document.title,
      send_to: "G-XXXXXXXXXX",
    });
  });
</script>

<!-- Worker (Partytown): gtag.js 本体と初期化 -->
<script
  type="text/partytown"
  async
  src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script type="text/partytown">
  window.dataLayer = window.dataLayer || [];
  window.gtag = function () {
    window.dataLayer.push(arguments);
  };
  gtag("js", new Date());
  gtag("config", "G-XXXXXXXXXX", { send_page_view: false });
</script>

考え方としては次のような分担でした。

Partytown 公式ドキュメントの Google Tag Manager ガイドにほぼ準拠した形です。理屈の上では動くはずでした。

調査

Claude Code に手伝ってもらって、以下の観点で調査しました。

1. Partytown の forward メカニズム

forward: ["dataLayer.push"] は、main thread の window.dataLayer.pushPartytown が独自のフォワーディングプロキシに差し替える仕組みです。main thread のコードが dataLayer.push(...) を呼ぶと、Partytown がその呼び出しをシリアライズして Service Worker / Worker 側に送信し、Worker 内の dataLayer に反映されます。

ここで重要なのが Partytown のローダーが動く前に window.dataLayer = [] を定義すると、Partytown がプロキシを貼れないケースがあるという点です。関連 issue としては QwikDev/partytown#27 があります。

私のコードは main thread 側で最初に window.dataLayer = window.dataLayer || []; window.gtag = function () {...}; を定義していたため、window.gtag のクロージャが古い dataLayer 参照を持ち続け、Partytown が後から貼ったプロキシには届かない という状態になっていました。

2. page_path は GA4 では非推奨

実装で使っていた page_pathUniversal Analytics (UA) 時代のパラメータで、GA4 では page_location(フルURL)が正式です。GA4 は page_location 未指定の場合 location.href で自動補完しますが、send_page_view: false + manual event の構成では config の初期化完了タイミング次第で event が正しく処理されない可能性があります。

参考: page_path vs page_location in GA4

3. Partytown + GA4 + View Transitions の既知問題

さらに調べると、この組み合わせに対する既知の不具合が複数見つかりました。

複数の技術ブログ(Rico Sta. CruzGarrett Digital)も 2026 年時点で「Astro の View Transitions と Partytown + GA4 は組み合わせるな」という結論を出しています。

つまり、実装の不具合と Partytown 側の既知不具合が重なっていたのが真の原因でした。

修正

結論として、この環境では Partytown を外して main thread で gtag.js を async ロードする素朴な構成に切り替えました。async 属性により初期描画はブロックされず、Lighthouse スコアへの実質的な影響も軽微です。

src/layouts/Layout.astro(修正後):

{
  PUBLIC_GA_ID && (
    <>
      <script
        is:inline
        async
        src={`https://www.googletagmanager.com/gtag/js?id=${PUBLIC_GA_ID}`}
      />
      <script
        is:inline
        set:html={`
          window.dataLayer = window.dataLayer || [];
          function gtag(){ dataLayer.push(arguments); }
          window.gtag = gtag;
          gtag("js", new Date());
          gtag("config", ${JSON.stringify(PUBLIC_GA_ID)}, { send_page_view: false });
          document.addEventListener("astro:page-load", function () {
            gtag("event", "page_view", {
              page_location: location.href,
              page_title: document.title,
            });
          });
        `}
      />
    </>
  )
}

ポイントは次の通りです。

astro.config.ts からは @astrojs/partytown import と integration 登録を削除し、依存パッケージも pnpm remove @astrojs/partytown で片付けました。

検証

修正後、本番デプロイして以下を確認しました。

  1. curl https://read-uncommitted.com/ | grep gtag で GA スクリプトと page_locationsend_page_view が HTML に埋め込まれていること
  2. ブラウザ DevTools の Network タブで https://www.google-analytics.com/g/collect?... リクエストが発火していること
  3. トップ→記事へのナビゲーションで 遷移ごとに新しい collect リクエストが飛ぶこと(View Transitions 経由の計測)
  4. GA4 リアルタイムレポートに自分のアクセスが計上されること

無事、GA4 のリアルタイムビューにアクセスが表示されるようになりました。

教訓

とまあ、色々書いていますが、ほとんどClaude Codeに助けてもらいました。特にGithub Issuesの調査などリサーチが途中で走り出すので結構驚きです。フロントエンドは普段やっていないので、こういう機会で都度学べるのは良いですね。


Share this post on:

Previous Post
TiDB WEEKLY 2026/04/24
Next Post
TiDB WEEKLY 2026/04/17