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

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

# 前提条件
- WebSiteにGAやGTMを未実装
- GAのプロパティー作成済み（トラッキングコード取得済み）
- GTMのコンテナ作成済み（トラッキングコード取得済み）

# ざっくりな構成図
![gtag.jsとGoogleタグマネージャー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70138/3136e6f6-2a33-a78d-bf60-4a1044c436e4.png)

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

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

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

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

# 下準備
## WebSiteにGTMのトラッキングコードを実装する
まずは、[GTM](https://tagmanager.google.com/)のトラッキングコードをWebSiteに実装します。

![gtm_03_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70138/5862a8ad-1b47-d783-b949-8ec3d35265f3.png)

![gtm_04_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70138/78c2094c-8104-4951-165e-d1e7d7b0eac5.png)

<img width="1440" alt="gtm_01_2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70138/1cce987d-edab-1bef-4c2f-43f02a3a08c0.png">

![gtm_02_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70138/92a30a51-1525-3dbe-9319-dc4d277267e6.png)

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

```javascript
<!-- 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>`直後の以下のコードを貼り付けます。
以下は参考コードです。

```javascript
<!-- 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](https://analytics.google.com/)でカスタムディメンションを作成しておきます。
`管理ページ`にアクセスします。

![ga_01_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70138/c403caba-1799-a179-35c2-fd4936641964.png)

![ga_02_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70138/cdaff973-fe3f-e11b-f98b-256e264606b8.png)

<img width="1440" alt="ga_03_1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70138/f681aae7-e4f4-162b-d61c-5431d6483194.png">

必要なだけ、カスタムディメンションを作成します。
例えば、[Google Analytics Standard版のトラッキングデータをBigQueryにためてLookerで分析をしたい 01](https://qiita.com/fbp-yamashita/items/f2fde8552dc09b81acb8#1-ga%E3%81%A7%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E3%83%87%E3%82%A3%E3%83%A1%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E4%BD%9C%E6%88%90)では、以下のようになります。

|  # | カスタムディメンション名 | 範囲 |
| :--- | :--- | :--- |
|  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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70138/72785796-35b3-8a45-0bef-698beb18a7b7.png)

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

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

![gtm_11_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70138/130de977-9b5c-a959-0d99-bd92186e72b8.png)

## `gtag.js`のコードを修正・追加してカスタムディメンションを送れるようにする
上記でトリガーを作成したら、いよいよタグを作っていきます。
タグは`カスタムHTML`を利用し、コードをカスタムしたものを登録していきます。

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

### コード例

```javascript
<!-- 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`は以下になります。
とてもシンプルですね。

```javascript
<!-- 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に送信しています。
詳細の説明は[本家の情報](https://developers.google.com/analytics/devguides/collection/gtagjs/custom-dims-mets)を参照してみてください。

```javascript
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_category`や`event_label`は何を設定するのかは自由ですので、自身の運用方針に合わせて設定してください。
こちらについての詳細については[本家の情報](https://developers.google.com/analytics/devguides/collection/gtagjs/events)を参照してみてください。

```javascript
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 button`と`after submit and view element`ではGAコードに内容に違いがありますが、説明簡略化のため、一つにしています
    - `after submit and view element`トリガーでは、`submit`イベントが必要
    - `click navi button`トリガーでは、`submit`イベントが不要

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

![gtm_13_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70138/f1c256d2-c267-ae20-9fcb-7baa166f7c39.png)

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

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

<img width="1440" alt="gtm_14_2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70138/a041dba0-3ad5-dcfb-921c-544fb7f2f184.png">

![gtm15_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70138/6076d68d-77e5-22b9-04b5-1e31531d6ebe.png)

### WebSiteでタグの発火状況を確認する
[WebSite](https://www.fbpp.jp/)にアクセスし、タグの発火状況を確認する。

![gtm_16_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70138/5786fd72-9cf2-e65b-d0e1-a97b27b19d8a.png)

![gtm_17_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70138/7d4279d3-279a-e445-1863-f1c6a6300efa.png)

![gtm_18_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70138/fd32a5cd-6c8f-fd3f-d34d-79db128752af.png)

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

# 併せて読みたい
- [Google Analytics Standard版のトラッキングデータをBigQueryにためてLookerで分析をしたい 01](https://qiita.com/fbp-yamashita/items/f2fde8552dc09b81acb8)