AstroでGA4が計測ゼロになる罠 — is:inlineのJSXエスケープでgtagが壊れる原因と対処【2026】
「HTMLにGA4タグはちゃんと入っているのに、レポートが0のまま」——これは実際に当方が運営する2サイトで起きた、なかなか気づきにくいバグです。原因は Astroの <script is:inline> 内で使ったJSXエスケープが、文字としてそのまま出力されていた こと。結果としてGA4の初期化が壊れ、データが一切送信されていませんでした。
同じ構成(Astro + Cloudflare Pages)で運用している方が踏みやすい罠なので、原因と直し方、そして「本当に直ったか」を確かめる方法までまとめます。
症状:タグはあるのにデータが届かない
- GA4の管理画面に「ウェブサイトから受信したデータはまだありません」と表示され続ける
- 本番HTMLには
gtag/jsのスクリプトも測定ID(G-XXXXXXXX)も出力されている - それなのに、リアルタイムにもレポートにも何も入らない
タグが「ある」ので一見正常に見えますが、初期化スクリプトが実行時エラーで止まっているため、計測ヒット(collect)が一度も飛んでいない状態でした。
原因:JSXエスケープが文字として出力される
問題のコードは、GA4初期化を import.meta.env.PROD && (...) のJSX式の中に置き、関数本体の波かっこをJSXエスケープで書いていました。レンダリングされた本番HTMLを取り出すと、次のように エスケープ記法がそのまま文字として出力 されていました(雰囲気を伝えるため記号は全角で表記します)。
function gtag(){'{'}dataLayer.push(arguments);
本来は function gtag(){ dataLayer.push(arguments); } になってほしいところが、エスケープ用に書いた {'{'} がそのまま文字列として残ってしまっています。ブラウザはこれを「関数本体は空、dataLayer.push(arguments) はトップレベル」と解釈し、コンソールに次のエラーを出して初期化を停止します。
Uncaught ReferenceError: arguments is not defined
arguments は関数の外には存在しないため、ここで処理が止まり、gtag('config', ...) まで到達せず、GA4へは何も送信されません。
ポイントは、JSXの式コンテキスト内では波かっこが式として扱われること。そして is:inline のスクリプト本文はそのまま出力されるため、エスケープ記法を書くと「直る」どころか、その記法自体が画面に出てしまうのです。
対処:set:html にテンプレート文字列で渡す
最も確実なのは、JSXのエスケープに頼らず set:html で生のJSを文字列として渡す方法です。バッククォートのテンプレート文字列の中なら、波かっこはJSの文字列の一部として扱われ、JSXに解釈されません。
<script is:inline set:html={ ` ...gtagの初期化JSをそのまま文字列で... ` } />
具体的には、window.dataLayer = window.dataLayer || [] と function gtag(){ dataLayer.push(arguments); }、gtag('js', new Date())、gtag('config', 'G-XXXXXXXX', { send_page_view: true }) を1つのテンプレート文字列にまとめ、set:html に渡します。set:html はその文字列をそのままスクリプト本文として出力するため、壊れずに初期化されます。
補足:この記事のように技術解説でコード片を載せるときは、フォーマッタ(Prettier等)が例示コードまで整形して「直して」しまうことがあります。あえて崩した例を見せたいときは、プレーンテキストのコードブロックにしておくと安全です。
「本当に直ったか」を必ず検証する
ここが最重要です。HTMLにタグが出ているかだけでは「直った」と言えません。 実際に計測ヒットが飛ぶところまで確認します。
ヘッドレスブラウザ(Playwright等)で本番ページを開き、ネットワークに collect リクエストが出るかを見ます。
POST https://www.google-analytics.com/g/collect?...&en=page_view => 204
en=page_view を含む collect が 204 で返れば、GA4へイベントが届いています。あわせてコンソールエラーが0であることも確認します。これで「タグはあるのに動いていない」状態を確実に潰せます。
教訓
- GA4の生存確認は「HTMLにタグがあるか」ではなく「collectが実際に飛ぶか」で行う
- Astroの
is:inlineで生のJSを書くときは、JSXのエスケープに頼らずset:htmlのテンプレート文字列 を使う - 新しくサイトを量産するときは、テンプレートにこの注意を明記しておくと、同じ事故を全サイトで繰り返さずに済む
地味ですが、計測がゼロだと「流入がないのか、測れていないのか」の区別がつかず、意思決定そのものが狂います。GA4を入れたら、必ず collect が飛ぶところまで見届けてください。
※ 本記事は当方の実運用での対応をもとにした技術メモです。測定IDなどは伏せています。Astroのバージョンや構成により挙動が異なる場合があります。
関連記事: CSPでGoogleアナリティクス・AdSenseがブロックされる罠 — GA4が動いているのに一部データが欠ける原因がCSP設定にある場合の対処法