Help us understand the problem. What is going on with this article?

Google Analyticsのgtag.js(カスタムディメンション)とGoogleタグマネージャーで、SPA(Single Page Application)のWebSiteをトラッキングできるようにする

背景

Google Analytics Standard版のトラッキングデータをBigQueryにためてLookerで分析をしたい 01にて、SPAで実装されたWeb Siteにカスタムディメンションで任意の情報をGoogle Analytics(以下GA)に送信する必要が出てきました。
gtag.jsでカスタムディメンションをGAに送信する方法が調べてもあまり出てこなかったので、自分が行なった方法を公開することにしました。

前提条件

  • WebSiteにGAやGTMを未実装
  • GAのプロパティー作成済み(トラッキングコード取得済み)
  • GTMのコンテナ作成済み(トラッキングコード取得済み)

ざっくりな構成図

gtag.jsとGoogleタグマネージャー.png

簡単に以下に流れをまとめます。

下準備

  1. WebSiteにGTMのトラッキングコードを実装する
  2. GAでカスタムディメンションを作成する

実装の流れ

  1. GTMにてトリガーを作成する(発火タイミングの調整)
  2. gtag.jsのコードを修正・追加してカスタムディメンションを送れるようにする
  3. GTMにてタグを作成する(gtag.jsを登録)
  4. GTMのプレビューモードにて、タグが発火しているかどうかを確認しながら、GAのリアルタイムでデータが送れていることを確認する

それでは、詳細を説明していきます。

下準備

WebSiteにGTMのトラッキングコードを実装する

まずは、GTMのトラッキングコードをWebSiteに実装します。

gtm_03_1.png

gtm_04_1.png

gtm_01_2.png

gtm_02_1.png

<head>内のなるべく上に以下のコードを貼り付けます。
以下は参考コードです。

<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-xxxxxxx');</script>
<!-- End Google Tag Manager -->

<body>直後の以下のコードを貼り付けます。
以下は参考コードです。

<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-xxxxxxx"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->

GAでカスタムディメンションを作成する

GAでカスタムディメンションを作成しておきます。
管理ページにアクセスします。

ga_01_1.png

ga_02_1.png

ga_03_1.png

必要なだけ、カスタムディメンションを作成します。
例えば、Google Analytics Standard版のトラッキングデータをBigQueryにためてLookerで分析をしたい 01では、以下のようになります。

# カスタムディメンション名 範囲
1 hitId ヒット
3 clientId ユーザー
4 datetime ヒット
5 sessionNumber ヒット
6 order_id ヒット

インデックスNoは後ほど実装で使うので番号を控えておきます。

実装

いよいよ実装に入ります。
GTMのプレビューモードで動作を確認しながら実装していくことになると思います。

GTMにてトリガーを作成する

まずはトリガーの作成です。
GTMでは、タグの発火タイミングをトリガーと表現しています。
要するに発火タイミングを作成するということです。

SPAがどう実装されているかにもよりますが、今回はページリンクが押下された際にイベントが発火するようにトリガーを作成します。
以下のパターンを作成します。

click navi button

  • トリガーのタイプ : クリック - リンクのみ
  • トリガーの発生場所 : 一部のリンククリック
  • イベント発生条件 : Click Classes 正規表現に一致 navi-item|action-button|etc…

要素をクリックされた際に発火するトリガーです。
設定イメージは以下の通り。

gtm_10_1.png

after submit and view element

  • トリガーのタイプ : 要素の表示
  • 選択方法 : CSSセレクタ
  • 要素セレクタ : div.email-thanks
  • このトリガーを起動するタイミング : 各要素が画面に表示されるたび
    • DOMの変化をモニタリング
  • このトリガーの発生場所 : すべての表示イベント

SPAのために、submit時にページ遷移がありません。
そのために、submit後に表示されるお問い合わせありがとうござました等の要素が表示された際に発火するトリガーとして設定しています。
設定イメージは以下の通り。

gtm_11_1.png

gtag.jsのコードを修正・追加してカスタムディメンションを送れるようにする

上記でトリガーを作成したら、いよいよタグを作っていきます。
タグはカスタムHTMLを利用し、コードをカスタムしたものを登録していきます。

実現したいこと

  • ページタイトルとページパスを都度、取得する
    • ページタイトルとページパスは#以降が単なる数字の場合があるので、コード内で書き換えることで対処する
    • desktop & tabletmobileで判別をする
  • お問い合わせ送信時のイベントはサブドメインごとに判別できるようにする
  • 処理順をCookieを利用して取得する
    • サブドメインを横断するように実装する

コード例

<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-xxxxxxxx-x"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  // get page path title
  newPagePath = '';
  newPageTitle = '';
  getPagePathTitle();
  eventLabel = 'submit_' + subDomainName;

  // get hitid
  hitId = '';
  datetime = '';
  getHitId();

  // set sequence_number
  setSequenceNumber();
  document.cookie = cookie_sequence_number;

  gtag('config', 'UA-xxxxxxxx-x', {
    'custom_map': {
      'dimension2': 'clientId',
      'dimension3': 'hitId',
      'dimension4': 'datetime',
      'dimension5': 'sequenceNumber'
    },
    'page_title': newPageTitle,
    'page_path': newPagePath,
    'hitId': hitId,
    'datetime': datetime,
    'sequenceNumber': sequence_number
  });
  gtag('event', 'click', {
    'event_category': 'form',
    'event_label': eventLabel,
    'value': 1
  });

  function getPagePathTitle(){
    // UA判定処理
    ua = '';
    checkUa = navigator.userAgent;
    if (checkUa.indexOf('iPhone') > 0 || checkUa.indexOf('Android') > 0 && checkUa.indexOf('Mobile') > 0) {
      ua = 'mobile';
    } else if (checkUa.indexOf('iPad') > 0 || checkUa.indexOf('Android') > 0) {
      ua = 'tablet';
    } else {
      ua = 'desktop';
    }

    // pathとtitleを設定
    pageTitle = document.title;
    newPagePath = location.pathname + location.hash;

    // www looker 判別
    subDomainName = location.hostname.replace(/(.*)\.fbpp\.jp/g,'$1');

    // pathとtitleを書き換え
    if(subDomainName === 'www'){
      if(ua === 'desktop' || ua === 'tablet'){
        if(location.hash){
          var pageTitleHash = location.hash.replace(/^#/g,'');
          if(pageTitleHash === '1'){
            pageTitleHash = 'top';
            newPagePath = '/#' + pageTitleHash;
            newPageTitle = pageTitleHash + ' | ' + pageTitle;
          }else if(pageTitleHash === '2'){
            pageTitleHash = 'our-services'
            newPagePath = '/#' + pageTitleHash;
            newPageTitle = pageTitleHash + ' | ' + pageTitle;
          }else{
            newPageTitle = pageTitleHash + ' | ' + pageTitle;
          }
        }else{
          newPageTitle = pageTitle;
        }
      }else{
        if(location.hash){
          var pageTitleHash = location.hash.replace(/^#/g,'');
          switch (pageTitleHash) {
            case '1':
              pageTitleHash = 'top';
              newPagePath = '/#' + pageTitleHash;
              break;
            case '2':
              pageTitleHash = 'our-services'
              newPagePath = '/#' + pageTitleHash;
              break;
            case '3':
              pageTitleHash = 'who-we-are'
              newPagePath = '/#' + pageTitleHash;
              break;
            case '4':
              pageTitleHash = 'about'
              newPagePath = '/#' + pageTitleHash;
              break;
            case '5':
              pageTitleHash = 'news'
              newPagePath = '/#' + pageTitleHash;
              break;
            case '6':
              pageTitleHash = 'careers'
              newPagePath = '/#' + pageTitleHash;
              break;
            case '8':
              pageTitleHash = 'contact-us'
              newPagePath = '/#' + pageTitleHash;
              break;
          }
          newPageTitle = pageTitleHash + ' | ' + pageTitle;
        }else{
          newPageTitle = pageTitle;
        }
      }
    }else if(subDomainName === 'looker'){
      if(ua === 'desktop' || ua === 'tablet'){
        if(location.hash){
          var pageTitleHash = location.hash.replace(/^#/g,'');
          newPageTitle = pageTitleHash + ' | ' + pageTitle;
        }else{
          newPageTitle = pageTitle;
        }
      }else{
        if(location.hash){
          var pageTitleHash = location.hash.replace(/^#/g,'');
          switch (pageTitleHash) {
            case '1':
              pageTitleHash = 'top';
              newPagePath = '/#' + pageTitleHash;
              break;
            case '2':
              pageTitleHash = 'news'
              newPagePath = '/#' + pageTitleHash;
              break;
            case '4':
              pageTitleHash = 'points'
              newPagePath = '/#' + pageTitleHash;
              break;
            case '5':
              pageTitleHash = 'partners'
              newPagePath = '/#' + pageTitleHash;
              break;
            case '6':
              pageTitleHash = 'innovation'
              newPagePath = '/#' + pageTitleHash;
              break;
            case '7':
              pageTitleHash = 'case-study'
              newPagePath = '/#' + pageTitleHash;
              break;
            case '9':
              pageTitleHash = 'installation'
              newPagePath = '/#' + pageTitleHash;
              break;
            case '10':
              pageTitleHash = 'about-us'
              newPagePath = '/#' + pageTitleHash;
              break;
            case '11':
              pageTitleHash = 'contact'
              newPagePath = '/#' + pageTitleHash;
              break;
          }
          newPageTitle = pageTitleHash + ' | ' + pageTitle;
        }else{
          newPageTitle = pageTitle;
        }
      }
    }else if(subDomainName === 'fivetran'){
      if(ua === 'desktop' || ua === 'tablet'){
        if(location.hash){
          var pageTitleHash = location.hash.replace(/^#/g,'');
          if(pageTitleHash === '2'){
            pageTitleHash = 'points';
            newPagePath = '/#' + pageTitleHash;
            newPageTitle = pageTitleHash + ' | ' + pageTitle;
          }else{
            newPageTitle = pageTitleHash + ' | ' + pageTitle;
          }
        }else{
          newPageTitle = pageTitle;
        }
      }else{
        if(location.hash){
          var pageTitleHash = location.hash.replace(/^#/g,'');
          switch (pageTitleHash) {
            case '1':
              pageTitleHash = 'top';
              newPagePath = '/#' + pageTitleHash;
              break;
            case '2':
              pageTitleHash = 'points'
              newPagePath = '/#' + pageTitleHash;
              break;
            case '4':
              pageTitleHash = 'feature'
              newPagePath = '/#' + pageTitleHash;
              break;
            case '5':
              pageTitleHash = 'connectors'
              newPagePath = '/#' + pageTitleHash;
              break;
            case '6':
              pageTitleHash = 'bi'
              newPagePath = '/#' + pageTitleHash;
              break;
            case '7':
              pageTitleHash = 'case-study'
              newPagePath = '/#' + pageTitleHash;
              break;
            case '8':
              pageTitleHash = 'installation'
              newPagePath = '/#' + pageTitleHash;
              break;
            case '9':
              pageTitleHash = 'about-us'
              newPagePath = '/#' + pageTitleHash;
              break;
            case '10':
              pageTitleHash = 'contact'
              newPagePath = '/#' + pageTitleHash;
              break;
          }
          newPageTitle = pageTitleHash + ' | ' + pageTitle;
        }else{
          newPageTitle = pageTitle;
        }
      }
    }
  }

  function getHitId(){
    // グローバル変数定義
    hitId = '';
    datetime = '';

    // clientIdを取得する
    var getc = '';
    var arck = document.cookie.split(';');
    arck.forEach(function(value){
      var content = value.split('=');
      if(content[0].trim() == '_ga'){
        getc = content[1].replace(/(^GA[0-9]+\.2\.)(.*)/g,'$2');
        return getc;
      }
    })

    // 日時を取得
    var date = new Date();

    // datetimeを設定
    var set_year = date.getFullYear();
    var set_month = date.getMonth()+1;
    var set_day = date.getDate();
    var set_hours = date.getHours();
    var set_minutes = date.getMinutes();
    var set_seconds = date.getSeconds();
    datetime = set_year + '-' + set_month + '-' + set_day + ' ' + set_hours + ':' + set_minutes + ':' + set_seconds;

    // timestampを設定
    var timestamp = date.getTime();

    // hitIdを設定
    hitId = timestamp + '_' + getc;
  }

  function setSequenceNumber(){
    var cookie_domain = 'fbpp.jp';
    var cookie_max_age = 60 * 30; // 30分
    var cookie_path = '/';

    var regex = new RegExp(/sequence_number/);
    var cookie_raw = document.cookie;

    if(regex.test(cookie_raw)){
      var arsq = cookie_raw.split(';');
      arsq.forEach(function(value){
        var content = value.split('=');
        if(content[0].trim() == 'sequence_number'){
          sequence_number = Number(content[1]) + 1;
          return sequence_number;
        }
      })
    }else{
      sequence_number = 1;
    }

    cookie_sequence_number = 'sequence_number=' + sequence_number + ';domain=' + cookie_domain + ';max-age=' + cookie_max_age + ';path=' + cookie_path;
    cookie_sequence_data = new Object();
    cookie_sequence_data.sequence_number = sequence_number;
    cookie_sequence_data.cookie_sequence_number = cookie_sequence_number;
    return cookie_sequence_data;
  }
</script>

参考までに、標準のgtag.jsは以下になります。
とてもシンプルですね。

<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-xxxxxxxx-x"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'UA-xxxxxxxx-x');
</script>

カスタムディメンションを送信する際の注意点

カスタムディメンションを送信する際は、まずcustom_mapでマッピングをする必要があります。
以下の'custome_map': {}の箇所になります。
マッピング後、それぞれ設定した例えば'hitId': hitIdなどで値をGAに送信しています。
詳細の説明は本家の情報を参照してみてください。

gtag('config', 'UA-xxxxxxxx-x', {
  'custom_map': {
    'dimension2': 'clientId',
    'dimension3': 'hitId',
    'dimension4': 'datetime',
    'dimension5': 'sequenceNumber'
  },
  'page_title': newPageTitle,
  'page_path': newPagePath,
  'hitId': hitId,
  'datetime': datetime,
  'sequenceNumber': sequence_number
});

イベントを送信する際の注意点

イベントを送信する際は、以下で行なっています。
event_categoryevent_labelは何を設定するのかは自由ですので、自身の運用方針に合わせて設定してください。
こちらについての詳細については本家の情報を参照してみてください。

gtag('event', 'click', {
  'event_category': 'form',
  'event_label': eventLabel,
  'value': 1
});

GTMにてタグを作成する

上記で作成したコードをタグに登録していきます。

GA Click Navi

  • タグの種類 : カスタムHTML
  • タグの優先度 : 10(最初に呼び出したいので大きめの数字を入れています)
  • タグの呼び出しオプション : 1回のイベントにつき1度
  • トリガー : click navi button after submit and view element
    • トリガーについては、click navi buttonafter submit and view elementではGAコードに内容に違いがありますが、説明簡略化のため、一つにしています
    • after submit and view elementトリガーでは、submitイベントが必要
    • click navi buttonトリガーでは、submitイベントが不要

参考までに以下がGTMの設定画面です。

gtm_13_1.png

GTMのプレビューモードにて、タグが発火しているかどうかを確認しながら、GAのリアルタイムでデータが送れていることを確認する

GTMのプレビューモードを使って、タグの発火状況を確認します。

プレビューモードを有効にする

gtm_14_2.png

gtm15_1.png

WebSiteでタグの発火状況を確認する

WebSiteにアクセスし、タグの発火状況を確認する。

gtm_16_1.png

gtm_17_1.png

gtm_18_1.png

まとめ

以上が、Google Analyticsのgtag.js(カスタムディメンション)とGoogleタグマネージャーで、SPA(Single Page Application)のWebSiteをトラッキングできるようのする方法です。
参考になればと思います。

併せて読みたい

fbp-yamashita
Looker、Fivetranの導入支援やDWH構築なんかをやっています。 FBP Partners CMO
https://www.fbpp.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away