JavaScriptの計測タグと最近のウェブページレンダリング(SPA、SSRなど)

最近増えているSPA、SSR、SSGとは何か

最近ではSPA(シングルページアプリケーション)、SSR(サーバサイドレンダリング)などといったウェブページの表示の仕組みを採用するサイトが増えている。これらのページではウェブ解析ツールの計測タグや広告のリマーケティングタグなどが正しく動作しないことがある。SPAの仲間にはSSR、SSGがあるが、これらの共通点はSPA内でのページの遷移時にページの読み込み処理(ロード)が行われない点である。

これらのページではページを最初に表示するときにだけ読み込み処理が行われる。そこでページのアセット(画像やJavaScriptのファイル)がまとめて読み込まれる。その後のページの切り替わり時にはページのボディ部分を部分的に書き換える処理が行われていく。ヘッダの画像などは最初に読み込まれたものがそのまま表示される。jQueryなどの大きなスクリプトファイルをもう一度読み込まれることもなく、最初に読み込んだものが使われ続ける。一方で各ページ固有の写真のファイルなどの新たなアセットだけが追加的に読み込まれることになる。

一方で従来のページはMPA(マルチページアプリケーション)といってこれらと区別することがある。MPAではページが切り替わるたびにページの読み込み処理が行われる。ページが切り替わるたびにページのアセットの取得を行い(画像やスクリプトファイルはキャッシュがあれば毎回ダウンロードしないが)、HTMLのレンダリングもゼロから行う。

SPAでは表示内容を書き換えたいその都度、更新する部分のコンテンツだけをサーバから取得し、レンダリングそのものはブラウザ側で行うものを指すことが多い。

SSRではレンダリングの大部分をサーバで行う、ページの表示内容をサーバ側で組み上げてからブラウザに渡す。ブラウザ側では最小限のレンダリング処理で済ませるものである。それに対してレンダリング処理のすべてをブラウザ側で行いものをクライアントサイドレンダリング(CSR)という。

SSGはクライアント(ブラウザ)側にとってはSSRとあまり変わらないが、サーバ側での処理に違いがある。SSRはブラウザからのリクエストのたびにHTMLを生成するのに対して、SSGはあらかじめHTMLを生成しておく。同じページでもタイミングや人によって表示内容が細かく変わる場合にはSSRでなければならないが、同じページでは表示する内容が同じ、つまり静的サイトの場合にがSSGでいい。ページの表示速度はサーバ側でのHTML生成の時間がない分、SSGのほうが高速になる。

厳密にはSPAでもSSRタイプのものとCSRタイプのものがある、SPAの対になるのがMPAで、SSRの仲間ががSSGで対になるのがCSR、でもSSGはSPAではないのだが、あまり気にする必要はない。計測上はただ一点、ページの読み込み処理をページ遷移のたびに行うか、行わないかを意識すればいい。ページ遷移のたびにページ読み込み処理が行われるのであれば従来通りのトリガー設定でいいが、そうでない場合はトリガーなどの計測設定を変更する必要がある。

一つのサイトにはMPAや複数の環境のSPAが混在する

一つのサイトがすべて一つのSPAやSSRだけでできているケースは多いわけではない。一部の階層のみSSRになっていたり、またSSRであっても異なるシステム(一方がNext.jsで、別の階層がReactなど)であるケースも多い。タグマネジメントにおいてはそういった複数のレンダリングシステムが混在した環境に対して、サイト全体で計測タグが正しく動作するように設定する必要がある。これは大変難易度が高い。

MPAページの基本

MPAページでは、GTMタグ読み込み時に発火するページビュートリガーが基本になる。データレイヤーからデータを取得したり、DOMの内容を取得したりする場合にDOM Readyを使うことがある。これについてはこれまでと変わりない。

SPA / SSR / SSGページの基本

サイト外や当該SPAページ以外から遷移してくるパターンと、SPAの同一ページ内で遷移をするパターンを両方サポートする必要がある。

初回読み込み時

SPAページそのものを読み込むタイミングである。純粋な閲覧開始や、MPAページから遷移してくる、他のシステムのSSRページから遷移してくるケースである。

SPA / SSR / SSGページの初回読み込み時にはGTMのタグも読み込まれるので、ページビュートリガーが適用される。MPAと同様にDOM Readyを使ってもいい。

次回以降のページ遷移

一度SPAページを読み込んだ後の、ページ内での遷移である。ここではページの読み込み処理は行われない。

簡単なのは「履歴の変更」トリガーを使うことである。「履歴の変更」(gtm.historyChange)とは、ブラウザでページの読み込みを伴わないURL変更が行われた時に発火するトリガーである。、厳密にはwindow.history.pushState()またはwindow.history.replaceState()メソッドが呼び出された時、イベントpopstateが検知された時、URLのハッシュ(フラグメント)部分が書き換えられた時のいずれかに該当した場合になる。

GA4の拡張計測機能では履歴の変更には対応しているが、あくまでGA4のみが対象であって、他のツールのタグや広告タグは対象外なので別途対応が必要となる。そうなると、GA4の拡張計測機能に依存せずにすべてタグマネージャ側で発火制御するほうが管理がしやすくなる。

SPAやSSRでも、最初のページ表示時(読み込み時)は該当しない。そこではページビュートリガーやDOM Readyが該当する。SPAやSSR内での次回以降のページ遷移時に「履歴の変更」トリガーが該当する。つまり従来の「ページビュー」トリガーや「DOM Ready」トリガーなどと、「履歴の変更」トリガーを組み合わせることですべてのページ遷移に対応できるようになる。

しかし「履歴の変更」トリガーの限界がある。履歴の変更トリガーではデータレイヤーやDOMの内容など、ページとのデータ連携は基本的に不可能になる。というのも多くのシステムでは履歴の変更処理後にページ内容の書き換えが非同期で進められるためである。つまり履歴の変更トリガー発生時には古いコンテンツされており、その後でページの表示処理が行われて新しいコンテンツになる。GTMの「履歴の変更」トリガーを使ってページビュー計測をすると、ページタイトルがそのURLと合わないものになるケースがあるのはこれが理由である。

「履歴の変更」トリガーの罠

「履歴の変更」トリガーではSPAやSSR内でのブラウザバックなどの際に想定通りにハンドリングされず、重複してトリガーが適用される(タグが重複発火する)ケースがある。その場合履歴の変更前後のURLが異なる時にのみ履歴の変更トリガーを発火させることで重複発火を回避できる。

「履歴の変更」発生時に変更前のURLはデータレイヤー変数gtm.oldUrlに、新URLはデータレイヤー変数gtm.newUrlに格納される。これらをそれぞれGTM変数として設定する。


ここで設定した2個のGTM変数の値が同じかどうかを判定するJavaScript変数を作成する。

この値がtrueであれば履歴の変更前後のURLが異なる(発火対象)、falseであれば前後のURLが同じということになる(非発火対象)。

したがって「履歴の変更トリガー」で、この変数がtrueになるケースに絞り込んでトリガーを作成する。以下は第1階層がaaabbb以外の全階層でこの絞り込んだ履歴の変更トリガーを実装する例である。

カスタムイベント

履歴の変更トリガーを使う場合はデータ連携はあきらめた方がいい。データ連携が必要な場合はページの切り替え時にアプリケーション(フレームワーク)側の設定でカスタムイベントを発火させ、それをトリガーとして個別のタグを発火させるようにする。

タグマネージャではなくウェブサイト側(フレームワークなど)でページ切り替え時の処理に以下の記述を入れる。

dataLayer.push({
  'event': 'custom_page_load', // (カスタムイベント名は何でもいい) 
  'category': 'cosmetics' // (連携するデータ)
    :
});

ここではcustom_page_loadという名前のカスタムイベントとしてページ切り替わりを意味するイベントを作った。GTM側でもこのカスタムイベントに対応したトリガーを作成する。

SPA / SSR / SSG内でのページ表示(遷移)時に発生させるイベント名はアプリケーションごとに別々にするのではなく、サイト全体で統一しておくといい。そうすると一つのトリガー設定ですべてのSPAやSSRなどのページに対して共通してページビュー計測できるようになる。

しかしSPAのシステム(フレームワーク)側からカスタムイベントを発生させる場合、技術的な実装上の観点から、前節の「次回以降のページ遷移」だけでなく初回読み込み時にも発生させざるを得ないのが普通である。そこでカスタムイベントを使う場合は、SPAやSSRの階層ではページビューやDOM Readyのトリガー自体を発火させないようにしておく必要がある。つまりページビュートリガー側で除外条件としてSPAのパスを含めておく。以下の例では第1階層がaaabbbを除外対象としている。

データレイヤーの罠

SPA / SSR / SSGページ内でページ遷移しても、データレイヤーは遷移前のページのものが残る。
dataLayerwindow.dataLayerと書くこともあるように、windowオブジェクトの中に保存される。windowオブジェクトはページ読み込みのタイミング(GTMのタグの読み込み時)でしかリセットされない。SPAやSSRページの中でページ遷移しても、明示的に書き換えたり消去したりしない限り、windowオブジェクトは古いものが残り、追記だけされていく。それを意識しないで使っていると、現在のページの内容とは違う値を採用してしまったり、データレイヤーの内容をトリガーの条件にしているケースではトリガーが適切に動作しなくなったりする。

そこでデータレイヤーの中身をリセットする処理が必要となる。データレイヤーの必要な中身は残しつつ、push()したデータの部分をリセットするメソッド

window.dataLayer.push(function() {
  this.reset();
})

を使う。

window.dataLayer = window.dataLayer || [];
window.dataLayer.push(function() {
  this.reset();
})
window.dataLayer.push({
  'category': 'cosmetics',
    :
});

とする。

SSGでHTMLテンプレートに

window.dataLayer = {
  ...
};

などと記述した場合、データレイヤーそのものを新規に作り直しているように見えるが、GTMそのものが動作しなくなる。GTMの動作に必要なデータレイヤーの中身まで消去されてしまうためである。その消去されるのを回避するために

window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
  ...
});

とすると、今度は古いdataLayerに追記するだけの処理になってしまい、古いデータが残ってしまう。なので先の記述をするのがベストプラクティスになる。

reset()メソッドを使ったときのデータレイヤーの中身はブラウザの開発者ツールで見るとこのようになる。

環境が混在する場合にどうやって分けるか

  • MPA用の従来の設定
  • SPA / SSR / SSGの簡易的な設定用「履歴の変更」トリガー
  • データ連携など、高度な設定のための「カスタムイベント」トリガー

これらを適切に分けないとタグの重複発火になり、計測ツールで特定のページだけページビューが何倍かになるなどの問題が発生する。

目的のタグを1ページで1回発火させるためには

  • ページビュー(データ連携する場合はDOM Ready)トリガーでSPAのカスタムイベント型の階層を除外したものを作る
  • 「履歴の変更」トリガーでSPAのカスタムイベント型の階層を除外したものを作る(SPAの履歴の変更型の階層)。MPAの階層は含まれていても問題ない
  • SPAのカスタムイベント型の場合のための共通のカスタムイベント(上記ではcustom_page_view)と、それに対応したトリガーを作る

これによると、URLの指定条件としてSPAのカスタムイベント型の階層だけを管理しておけばいいので、管理自体はかなり楽になる。

[公開日:2024年11月7日]

Google の記事一覧