測定IDを保存し、トップページの閲覧がRealtimeに現れた。
これで設定は終わったように見える。
ところが、Next.jsの画面遷移を数えていなければ2ページ目以降が欠け、Google側とアプリ側の両方で遷移を数えていれば同じ閲覧が二つ並ぶ。
GA4の導入では、タグが読み込めたことと、正しいpage_viewが継続して届くことを分けて確かめる必要がある。
この記事は、Google Analytics 4のpropertyとweb data streamを用意し、このブログの現行実装へMeasurement IDを設定して、Cookie同意とSPA遷移を検証する手順を説明する。
実装とGoogle公式資料は2026年7月26日に確認した。
掲載するIDはすべて例示用であり、実際のMeasurement ID、検証token、account名は含めていない。
Cookie同意からGA4の読込、SPA遷移、RealtimeとDebugViewでの確認までを結ぶ流れ*図: 同意前はGoogle tagを読み込まず、同意後の初回表示とクライアント遷移を別々に計測し、送信結果をブラウザーとGA4の両側で照合する。*
property、data stream、Measurement IDを分ける
GA4の設定画面には、account、property、data streamという異なる単位がある。
この三つを同じ設定値として扱うと、管理画面でどの値をブログへ入れるのか分かりにくくなる。
propertyは、レポート、イベント、利用者権限などをまとめる計測先である。
web data streamは、そのpropertyへWebサイトのデータを送る入口である。
Measurement IDはweb data streamに付くG-で始まる識別子であり、このブログへ設定する値はこれである。
Google AnalyticsのAdminで次の順に作成する。
- Analytics accountがなければ作成する。
- 対象サイト用のGA4 propertyを作り、reporting time zoneとcurrencyを運用条件に合わせる。
- AdminのData StreamsからWebを選び、本番サイトのHTTPS URLと識別しやすいstream名を登録する。
- Web stream detailsを開き、Stream detailsに表示されるMeasurement IDをコピーする。
この時点では、propertyを作っただけではデータは届かない。
反対に、G-XXXXXXXXXXをコードへ書いただけでも、存在しないstreamや別propertyのIDなら目的のレポートには届かない。
コピー元のproperty名とstream URLを画面上で確認してから、IDだけを設定画面へ移す。
このブログへMeasurement IDを保存する
現行ブログには、管理画面のマーケティング設定に「Google Analytics 測定ID」と「Analytics」の有効切替がある。
本番では保存済みのmarketing settingsを読み、保存値がない場合はNEXT_PUBLIC_GA_MEASUREMENT_IDをfallbackとして使う。
そのため、通常の運用では管理画面へ例示形式のIDを入力し、Analyticsを有効にして保存すればよい。
RootLayoutはrequestごとにmarketing settingsを読み、Analyticsが無効なら空文字列をGoogleAnalytics componentへ渡す。
component側はG-から始まる形式を検証し、空または不正な値ならtagを読み込まない。
保存後は本番URLを新しいnavigationで開き、HTMLが表示されたという事実だけでなく、同意後のnetwork requestまで確認する。
環境変数を使う場合も、repositoryへ実値を書き込む必要はない。
.env.exampleのplaceholderと同じ形式でdeployment環境へ設定し、build logや記事のスクリーンショットへ実値を残さない。
Measurement IDはGoogle tagの送信先を選ぶ識別子だが、実値を記事へ転載する理由にはならない。
同意前にtagを読み込まない現行動作
このブログのGoogleAnalytics componentは、localStorageに保存されたmarketing同意をuseSyncExternalStoreで購読する。
利用者がまだ同意していない場合と拒否した場合は、googletagmanager.comのloaderもinline configも追加しない。
利用者が同意すると、componentはloaderとconfigを一つずつ追加し、analytics_storageをgrantedへ更新してから計測を始める。
現行の順序は次のとおりである。
- 同意状態とMeasurement IDを検査する。
- 同意済みの場合だけGoogle tagのloaderを追加する。
- consentのdefaultとして
ad_storageとanalytics_storageをdeniedにする。 analytics_storageだけをgrantedへ更新する。jsとconfig commandを実行する。- 同意が撤回されたら両storageを
deniedへ更新し、script elementとそのdocument内のruntimeを除去する。
別tabで拒否へ変更された場合はstorage eventを受け取り、読み込み途中のoptional scriptを止めてdocumentを再読込する。
ただし、撤回より前にGoogleへ送信済みのeventまでブラウザーから取り消せるわけではない。
撤回後の送信を止める動作と、すでに収集されたデータの管理は別の問題である。
このcleanupは既存のAnalytics cookieを削除しないため、Cookie削除やdata deletionが必要な要件は別途実装する。
この実装を「Consent Mode v2へ完全対応済み」と説明することはできない。
Googleの現在のconsent modeにはad_storageとanalytics_storageに加え、広告用user dataのad_user_dataと広告personalizationのad_personalizationがある。
現行componentは後者二つを設定せず、拒否中はtag自体を読み込まないため、Googleが区別するbasic consent modeに近いconsent-gated loadingである。
広告計測を含む完全なv2実装、地域別default、CMP連携、法的要件までを保証するものではない。
必要なconsent typeと表示文言は、利用するGoogle製品、配信地域、組織の方針を確認して別途設計する。
初回表示とSPA遷移の所有者を一つにする
通常のdocument loadでは、gtag('config', ...)が既定でpage_viewを送る。
一方、Next.jsのクライアント遷移ではHTML documentを読み直さないため、初回loadだけを見ているtagでは次のURLが自動で数えられるとは限らない。
GoogleのSPAガイドは、History APIの変更またはcustom eventを使ってvirtual page viewを発火し、各screen interactionとreferrerを確認するよう案内している。
現行componentはアプリ側でSPA遷移を所有する。
pushStateとreplaceStateをpatchし、popstateも購読して、pathname + searchが変わったときにgtag('config', measurementId, { page_path })を呼ぶ。
直前に送ったpathと同じ値は捨て、window.gtagの準備前に起きた遷移は最新pathだけを保留して送る。
初回pathはconfigの既定page_viewへ任せるため、同じ初回表示を手動送信しない。
現行componentが遷移時に明示するのはpage_pathだけであり、page_locationとpage_referrerを引数で更新しているわけではない。
Googleの推奨どおりvirtual referrerを維持できているかはDebugViewで確認し、最初のdocument referrerのままなら明示的なpage_view eventと前のURLを保持する設計へ改める必要がある。
ここで、GA4 web streamのEnhanced measurementでも「Page changes based on browser history events」を有効にすると、同じHistory API変更をGoogle側も検出し得る。
アプリ側のlistener、Enhanced measurement、Google Tag ManagerのHistory triggerを重ねると、同じ遷移に複数のpage_viewが発生する。
送信経路は一つに決める。
現行componentを保つ場合は、web streamのEnhanced measurementでPage viewsの詳細を開き、「Page changes based on browser history events」を無効にして、クライアント遷移をcomponentだけに任せる。
Google側の自動計測へ統一する場合は、先にcomponentのhistory listenerを削除し、Enhanced measurementのPage loadsとbrowser history changesを有効にして検証する。
Google Tag Managerを採用する場合も、GA4の自動history計測とGTM triggerを同時に有効にしない。
すべてのpage_viewを明示的なevent commandで送る別設計なら、初期configへsend_page_view: falseを指定し、初回も含めて一つの共通処理から送る方法がある。
現行実装はその方式ではない。
一般的なsnippetからsend_page_view: falseだけを持ち込むと、初回page_viewが消えるので注意する。
なお、現行のpathはquery stringを含むがhashを含まない。
Hashだけでscreenを切り替えるアプリへ流用するなら、現在のlistenerでは別pageとして扱われない。
このブログのようなHistory APIベースのrouteでも、locale変更、query付きURL、戻る、進むをそれぞれ試す必要がある。
URLとevent parameterへ個人情報を入れない
Googleのpolicyは、Googleが個人を識別または認識できる情報をAnalyticsへ送らないよう求めている。
メールアドレス、個人の電話番号、氏名、問い合わせ本文などをevent parameterへ入れるわけにはいかない。
画面に表示していない値でも、送信payloadへ含めれば同じである。
特にこのブログの現行componentはpathname + searchをpage_pathとして送る。
したがって、メールアドレス、電話番号、氏名、認証tokenなどをquery parameterへ置く設計は、GA4導入前から避けなければならない。
Google Analyticsのdata redactionにはメール形式と指定query parameterを除く機能があるが、Googleはbest effortと説明している。
redactionを、アプリ側で個人情報を送らない設計の代わりにはできない。
custom eventにも同じ制約を適用する。
trackEventへ渡す値は、locale、buttonの種類、公開記事のslugなど、分析目的に必要な低cardinalityの値へ限定する。
検索窓の全文、contact formの内容、chat入力、自由記述error messageは送らない。
送信前のallowlistをコードで定めると、後から追加したUIが意図せず自由入力を流す事故を減らせる。
本番で一つのpage_viewを追う
local環境でtag elementを見つけただけでは、本番のCSP、同意状態、広告blocker、Measurement IDの取り違えを検出できない。
検証は、ブラウザー、DebugView、Realtimeの順に同じ操作を追う。
- 保存済み同意を消した新しいbrowser profileで本番URLを開く。
- 同意前に
googletagmanager.com/gtag/jsとAnalyticsのcollect requestが送られていないことをNetwork panelで確認する。 - 同意後にloaderが一つだけ追加され、Google tagへのrequestが成功することを確認する。
- Google Tag Assistantまたはdebug modeを有効にし、GA4のAdminからDebugViewを開く。
- 最初のpage_viewを確認し、日本語記事、英語記事、query付きURLへクライアント遷移する。
- 各遷移でpage_viewが一つだけ増え、
page_locationとpage_referrerが直前の画面に合わせて変わることを確認する。 - browserの戻ると進むを使い、同じ規則で一つずつ増えることを確認する。
- 同意を撤回し、その後の遷移で新しいcollect requestが出ないことを確認する。
- Realtimeで直近5分と30分のactivityを確認する。
DebugViewはdebug modeを有効にしたdeviceのeventをほぼリアルタイムで追う場所であり、通常reportの代替ではない。
Realtimeにも一部のdimensionとuser acquisition処理に制約がある。
GoogleのData freshness文書では、処理中の通常reportは変化することがあり、data processingに24時間から48時間かかる場合があると説明されている。
Realtimeにeventがあり、通常reportにすぐ現れないだけなら、設定失敗と断定せず翌日以降も確認する。
症状から送信経路を絞る
| 症状 | 確認する場所 | よくある原因 |
|---|
| 同意後もrequestがない | 管理画面、Elements、Network | Analyticsが無効、ID形式が不正、同意が拒否、CSPまたは拡張機能によるblock |
| トップページだけ記録される | History API、DebugView | SPA遷移listenerが動いていない、hashだけが変わっている、別router方式を使っている |
| 遷移ごとに二件記録される | DebugViewのtimestampとparameter | componentとEnhanced measurement、またはGTMが同じhistory changeを送っている |
| 古いpathが後から届く | Networkとnavigation順序 | tag準備前のqueueがstale、非同期処理が現在URLを再確認していない |
| DebugViewにdeviceが出ない | debug mode、同意、property | debug modeが無効、別propertyのID、browser側でrequestがblockされている |
| Realtimeにはあるがreportにない | Realtime、Data freshness | 通常reportが処理中で、24時間から48時間の範囲にある |
| URLに個人情報が見える | page_location、page_path、query | アプリが自由入力やtokenをURLへ含め、redaction前の値を送っている |
重複を調べるときは、GA4画面の合計値だけを眺めず、一回のclickに対応するNetwork requestとDebugView eventを時系列で並べる。
二件のparameterが同じなら二重送信を疑い、異なるならredirectや別画面の発火条件を調べる。
この切り分けなら、計測値の違和感を推測だけで直さずに済む。
Search ConsoleのclickとGA4のsessionは同じ数にならない
Search ConsoleのPerformance reportは、Google検索結果でのimpression、click、CTR、average positionを扱う。
GA4は、サイトへ到着したあとにtagが送ったpage_view、event、session、user behaviorを扱う。
前者のclickと後者のsessionは測定地点も成立条件も異なるため、一対一で一致する指標ではない。
このブログでは、同意前と拒否中にGA4 tagを読み込まない。
Search Consoleでclickが数えられても、利用者が同意しない、JavaScriptがblockされる、到着前に離脱するなどの条件では対応するGA4 eventがない場合がある。
日付境界、attribution、URLのgroupingも確認し、単純な差分を実装障害と決めつけない。
GA4のweb data streamとSearch Console propertyをlinkすると、Analytics内でorganic search queryとlanding pageに関するreportを利用できる。
それでもSearch Console metricsとAnalytics metricsの定義が同じになるわけではない。
Search Console側のproperty作成、所有権確認、sitemap送信は、Next.jsブログにGoogle Search Consoleを設定するで分けて説明している。
Google公式参考資料