どうもカッツプロダクション代表のカッツです!久々にGTMとがっつり格闘してました、、、

今回は、SPA(シングルページアプリケーション)のサイトで、GA4のページビューをGTMでちゃんと計測できるようになるまでの記録です。結論から言うと、公式手順をベースに「前のページのURLを保存するタグ」を足すことで、遷移ごとのページビューと「どこから来たか(page_referrer)」まで取れるようになりました。ただ、設定は超大変でした!

せっかちな設定を迅速に見たい人はこちら

※2026年11月時点、とある案件のサイトでの設定内容です。ドメイン・GTMのコンテナID・測定ID等は伏せています。

SPAだとGA4が普通に計測できない理由

SPAは、ページを移動するたびにHTMLを読み込み直すんじゃなくて、JavaScriptで画面だけ書き換えて、URLも書き換える仕組みです。サクサク動くのはいいんですが、GA4から見ると最初の1回しかページを読み込んでないことになるんですよね。

なので何もしないと、ページビューが最初の1回しか計測されない、という状態になります。

拡張計測の自動計測だけじゃダメなの?

実はGA4には、ブラウザの履歴の変化を見て、自動でページビューを送る拡張計測の機能もあります。公式にも、History API(URLを書き換えるブラウザの機能)で画面を切り替えるSPAなら自動で計測できる、と書いてあります。

なので最初は「それでいけるじゃん!」と思ったんですが、、、GA4のリアルタイムで見てたら、ズレまくってました。

  • ページタイトルが前のページのままで送られる
  • 1回の遷移でページビューが2回カウントされる
  • 参照元(page_referrer)が全部同じになる

原因は、SPA側が画面を切り替えた後にタイトルを書き換えていたり、1回の遷移でURLの書き換えが複数回起きていたりするからっぽいです(あくまで推測です)。自動計測は「履歴が変わったら、とりあえず送る」なので、サイトの作りのクセをそのまま拾っちゃうんですよね多分。

なので、GTMで「いつ・何を送るか」を自分で決める方式にしました。その場合は二重計測を防ぐために、GA4管理画面のデータストリームで、「ページビュー」の詳細設定にある「ブラウザの履歴イベントに基づくページの変更」をオフにしておきます(公式の手順でもオフにするよう書かれてます)。

サイトの作りによっては、自動計測だけで問題なく取れることもあると思います。まずはGA4のリアルタイムやDebugViewで、タイトル・回数・参照元がちゃんと取れているか見てみるのがおすすめです。

SPAの計測は2018年以来

実はSPAの計測をやるのは、2018年にとあるミッションで店頭用のタッチパネルアプリをPWA+SPAで作ったとき以来です。当時は「pushState(URLを書き換えるブラウザの機能)って出てきたばかりだし、なんか不安だな〜」と思っていたのと、jQueryベースで作っていたので、onclickで頑張って計測した記憶があります(笑)

で、今回あらためて調べてみたら、pushState自体は2010〜2011年ごろには主要ブラウザで使えるようになってたんですよね。2018年時点で「出てきたばかり」ではなかった、、、(すいません)

ただ、初期のブラウザでは「ページを開いただけでpopstate(戻る・進むのイベント)が飛ぶ」みたいな挙動のバラつきがあったそうです。しかも、どのブラウザでも安心して使える技術扱いになったのは2018年1月。なので、当時の自分の不安も、あながち的外れではなかったのかもです。

ちなみにGTMにも、2014年の時点で今の「履歴の変更」トリガーの前身(History Listener)がもうあったという(笑)。知ってたらonclickで頑張らなくてよかったかも、、、

Nuxt.jsやNext.jsで作るなら、ルーターと連携してくれるライブラリがあるので楽ちんなんですが、、、今回はあえてGTMでやることにしました。GTMでやれば、マーケ側の計測設定と、制作側のコードを切り分けられて綺麗じゃないですか。

ところが、うまくいく情報が見つからないんです(涙)。自分の検索スキルの問題なのか、そもそも良い情報が出回ってないのか、、、GeminiさんとClaudeさんに協力してもらって、なんとかうまくいくところまで持っていきました。

最終的な構成

最終的にはこんな構成に落ち着きました。

種類名前役割トリガー
GoogleタグGa4設定普段どおりのGA4の土台(初回のページビューもここ)初期化 – All Pages
カスタムHTMLspa向け-前の履歴保存「今のURL」と「前のURL」を記録する初期化 – All Pages、履歴の変更
GA4イベントGa4設定-spa対策遷移ごとにpage_viewを送る履歴の変更
変数(カスタムJS)History Old URL記録した「前のURL」を読むだけ–
変数(カスタムJS)Page Title今のページタイトルを返す–
GTMのタグ一覧。Ga4設定、Ga4設定-spa対策、spa向け-前の履歴保存の3つのタグと配信トリガー

履歴の変更トリガー

GTMの「履歴の変更」トリガーを使います。条件はHistory Sourceが正規表現でpushState|popstateに一致したとき。新しい画面に進んだとき(pushState)と、ブラウザの戻る・進む(popstate)のときだけ動くようにしてます。(ここはSPAの作りに左右されるので注意してください。)

GTMの「履歴の変更」トリガー設定。History Sourceが正規表現でpushState|popstateに一致

前のページを保存するカスタムHTMLタグ

ここが今回のキモです。「今のURL」を記録しておいて、遷移したらそれを「前のURL」に回す、というシンプルな仕組みです。

カスタムHTML(spa向け-前の履歴保存)

<script>
(function() {
  var cur = window.location.href;
  if (typeof window._gtm_cur_location === 'undefined') {
    // 最初のページ読み込み:外部からの参照元を使う
    window._gtm_prev_location = document.referrer || '';
  } else if (window._gtm_cur_location !== cur) {
    // SPA遷移:直前に記録していたURLが「前のページ」になる
    window._gtm_prev_location = window._gtm_cur_location;
  }
  window._gtm_cur_location = cur;
})();
</script>

トリガーは「初期化 – All Pages」と「履歴の変更」の2つ。最初のページではdocument.referrer(外部からの参照元)を「前のURL」にして、SPAで遷移したら、直前に記録していたURLを「前のURL」に回します。

カスタムHTMLタグ「spa向け-前の履歴保存」の設定。前のURLを保存するスクリプトと、初期化・履歴の変更のトリガー

読み取り専用のカスタムJS変数

記録した「前のURL」は、カスタムJavaScript変数で読むだけにしてます。

カスタムJavaScript変数(History Old URL)

function() {
  return window._gtm_prev_location || '';
}
カスタムJavaScript変数「History Old URL」の設定。window._gtm_prev_locationを返すだけの内容

カスタムJavaScript変数(Page Title)

function() {
  return document.title;
}
カスタムJavaScript変数「Page Title」の設定。document.titleを返すだけの内容

page_viewを送るイベントタグ

GA4イベントタグで、イベント名はpage_view。トリガーは「履歴の変更」です。パラメータはこんな感じ。

  • page_location:{{Page URL}}(今のURL)
  • page_title:{{Page Title}}(今のページタイトル)
  • page_referrer:{{History Old URL}}(前のURL)

そして大事なのが、詳細設定の「タグの順序付け」。このタグが発効する前に「spa向け-前の履歴保存」を配信するように設定してます。こうしないと、前のURLの記録が間に合わずに、page_referrerがズレることがあるんですよね。

GTMのGA4イベントタグ設定画面。イベント名page_view、パラメータにpage_location、page_title、page_referrerを指定。タグの順序付けで「spa向け-前の履歴保存」を先行配信する設定が「超重要」と強調されている。
忘れてましたがイベント名 page_view も決め打ちです

スクショにあるupdate:trueは、公式手順だと「Googleタグ」側に入れる設定パラメータです。今回の構成のようにイベントタグ側に入れて意味があるのかは、正直検証できてません、、、(たぶん無くても動きます)

ハマったのは「変数が勝手に書き換わる」問題

最初は、「前のページのURL」をカスタムJavaScript変数の中で記録して、そのまま返す作りにしてました。ところが、変数を参照するたびに値が書き換わってしまうんです。

GTMの変数って、タグが配信されるときだけじゃなくて、トリガーの判定やイベントが発生するたびに何度も評価されるんですよね。なので、変数の中で値を書き換える処理があると、評価されるたびに「前のURL」がどんどん更新されて、page_referrerが今のURLと同じになったりするわけです。

なので、記録する役(カスタムHTMLタグ)と、読むだけの役(カスタムJS変数)に分けた、というのが今回の構成です。分かってしまえば単純なんですが、ここに気づくまでが長かった、、、

「spa向け-前の履歴保存」タグの設定。絞り込んだpushState系の履歴変更でのみ発火して変数に入る

公式手順との違い

GA4公式の「Google タグ マネージャーでシングルページ アプリケーションを測定する」という手順では、ざっくりこうなってます。

  • 拡張計測の「ブラウザの履歴イベントに基づくページの変更」をオフ
  • 「履歴の変更」トリガーを作る
  • page_locationを更新するGoogleタグ(トリガーなし)を作って、page_viewのイベントタグのセットアップタグにする
  • page_viewのイベントタグを「履歴の変更」で配信

で、公式手順にはpage_referrerが含まれていません。注記に「SPA内の前の仮想ページを参照元として計測したいなら、前のページの場所を保存するカスタム変数を実装する必要がある」と書いてあるだけで、そのやり方は載ってないんです。

もう1つ、初回のページ読み込みでpage_viewをどう送るかも書かれていません。タグIDのところで「最初のGoogleタグと同じID」と出てくるだけで、その最初のGoogleタグをどのトリガーで配信するのか、初回のpage_viewを誰が送るのかは書いてないんです。SPAに慣れてない人だと、ここで「あれ、最初のページは?」ってなりますよね。今回は普段どおり、Googleタグ(Ga4設定)を「初期化 – All Pages」で配信して、初回のpage_viewはそこから送る形にしてます。

タグ一覧。Ga4設定は初回読み込み用で、リロード時もこのタグで補完する

結局、載ってなかったpage_referrerの部分を自分で作ったのが、今回の「前の履歴保存」タグです。公式は「必要なら自分で作ってね」というスタンスです!

プレビューで4項目を確認

GTMのプレビューモードで、この4つが揃っていることを確認しました。

  1. 最初のページ読み込みで、page_viewが1回だけ送られる(Ga4設定のGoogleタグから)
  2. SPAで遷移するたびに、page_viewが1回ずつ送られる(二重計測になってない)
  3. page_locationが遷移後のURLになっている
  4. page_referrerが遷移前のURLになっている
GTMのプレビュー画面。概要タブで「Ga4設定」タグが1回配信されたことが表示されている。
GTMプレビューの履歴イベントで、Ga4設定-spa対策とspa向け-前の履歴保存タグが配信された画面。
GTMプレビューでのGA4イベントタグ詳細。イベント名page_viewに対し、page_location、page_title、page_referrerなどのパラメータが設定されている様子。

ちなみにプレビュー中は、最初のURLに?gtm_debug=...が付くので、page_referrerにもそれが入ってます。本番では付かないので気にしなくて大丈夫です。

そもそもSPAにする必要ある?

ここまでやっておいてなんですが、、、個人的には、Webアプリ(SaaSの管理画面とか)ではない、コーポレートサイトやメディアサイトなら、普通のページ遷移(同期遷移)のほうが不具合は少ないと思いますマジで!

  • 計測:今回みたいな苦労がそもそもいらない
  • OGP:フレームワークでSSRしてれば問題ないけど、素のSPAだと、SNSのシェア画像やタイトルがページごとに出ないことがある
  • 外部ツールとの相性:フォームやチャット、広告タグなどは「ページ遷移する前提」のものが多い
  • バグの切り分け:どこで何が起きたか追いやすい

アプリっぽい操作感がどうしても必要なら別ですが、「なんとなくイマドキだから」でSPAにすると、あとで計測担当が泣くことになるかも、、、(笑)

まとめ

  • SPAは画面だけ書き換えるので、何もしないとページビューが最初の1回しか計測されない
  • GA4の自動計測だとタイトル・回数・参照元がズレたので、自動はオフにしてGTMの「履歴の変更」トリガーでpage_viewを送る形にした
  • 公式手順にはpage_referrer(前のページ)の取り方も、初回読み込みの扱いも載ってないので、前のURLを保存するカスタムHTMLタグを自作した
  • GTMの変数は何度も評価されるので、値を書き換える処理はタグ側に、変数は読むだけにする
  • Webアプリではないコーポレート・メディアサイトなら、そもそも普通のページ遷移のほうが楽

参考になれば幸いです!ツッコミどころあれば連絡くださいね。AIの困りごとあれば相談にのれますよ!