nuxtでGTMタグをheadとbodyに埋め込む（非SPA、モジュール不使用、シンプル）


クライアント案件で、GTMタグを埋め込みたいとの連絡をいただいた。

通常の静的サイトであれば、GTM用のタグを
`<head>`内と`<body>`直後の全ページに設置するだけで良いのだけれど、
これをnuxtでgenerate出力した場合のサイトにシンプルに適用する方法について共有します。
（20/02/19 時点で動作確認）

## 対象サイト
- nuxt.js（v2.0.0）で構築
- universalモード（非SPA）
- generateで生成した静的Webサイト（下層ページあり）

## やりたいこと
GTMから発行される下記タグを全ページに設置する（あるいはそれと同等のことをする）。

- `<head>`〜`</head>`に

```html:*.html
<!-- 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>`直後に

```html:*.html
<!-- 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) -->
```

## いろんなやり方ある・・・が、自社案件じゃないと歯がゆい
#### 公式モジュール

nuxt公式が出している[GoogleTagManager用のモジュール](https://github.com/nuxt-community/modules/tree/master/packages/google-tag-manager)を使ってみる。

ところが情報色々ぐぐってみると、
デフォルトではページビューの計測オプションがfalseになっており、
計測したい場合はこちらのオプション有効化することに加え、
管理画面側でページビューを拾うためのタグの設定などが必要だそう。
> 
- [Nuxt SPAのPVを @nuxtjs/google-tag-manager を利用して計測する](https://tech.actindi.net/2018/11/02/084447)
-  [Nuxt.js(v2)でgenerate納品する前にやっておきたい設定 - GTMの設置](
https://qiita.com/amishiro/items/11bd642728f6b5838189#gtm%E3%81%AE%E8%A8%AD%E7%BD%AE)

先方管理画面のログイン権限がないような今回のケースだと、
担当者にタグの設定を一発で的確に指示できる自信があまりないし、
設定の不備による計測ミスなど状況をややこしくしたくない、、
実装サイドだけで完結させられないか、他の方法を検討。

#### 自前でプラグイン実装
これも[公式](https://ja.nuxtjs.org/faq/google-analytics/#google-%E3%82%A2%E3%83%8A%E3%83%AA%E3%83%86%E3%82%A3%E3%82%AF%E3%82%B9%E3%82%92%E4%BD%BF%E3%81%86%E3%81%AB%E3%81%AF-)にのっている方法で、
プラグインファイルを作って`nuxt.config.js`で読み込めばいいよというやつ。

> 
- [NuxtでGTMタグを埋め込む方法](https://scrapbox.io/gyarasu/Nuxt%E3%81%A7GTM%E3%82%BF%E3%82%B0%E3%82%92%E5%9F%8B%E3%82%81%E8%BE%BC%E3%82%80%E6%96%B9%E6%B3%95)
- [NuxtにGTMを入れて、ページタイトルをGAに送る](https://qiita.com/okeita0805/items/00dde7b7033a549e618e)

この方法は、nuxtに対して「毎回遷移時にコード送ってね」というコードを書いているので、
（つまるところそれを簡単に使えるようしてくれているのが先程のモジュールなわけで、）
nuxtの内部で処理されるため、`<head>`とか`<body>`にコードが出力されるわけではない。
し、どちらにせよイベント送信してゴニョゴニョするってことは
受信側でタグ設定が必要になりそうなので、ナシ。

いやいや、もっと<b>シンプル</b>に・・<b>該当の箇所に埋め込みたいだけ</b>なのだ・・🐵
ということで調べて、たどり着きました。


## 【結論】nuxt.config.js（Vue meta）で記述できる

nuxt.jsのAPI:headを利用し、vue-metaのルールに則って記述するだけで大丈夫でした。
> 
- [NUXT.JS - API: head メソッド](https://ja.nuxtjs.org/api/pages-head/)
- [Vue Meta - API Reference](https://vue-meta.nuxtjs.org/api/)

nuxt.config.js に下記を追記しましょう。

```.js:nuxt.config.js
const gtmID = 'GTM-XXXXXXX'
const gtmHeadTag = `(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','${gtmID}');`
const gtmBodyTag = `<iframe src="https://www.googletagmanager.com/ns.html?id=${gtmID}" height="0" width="0" style="display:none;visibility:hidden"></iframe>`

export default {
  head: {
    script: [
      {
        hid: 'gtmHead',
        innerHTML: gtmHeadTag
      }
    ],
    noscript: [
      {
        hid: 'gtmBody',
        innerHTML: gtmBodyTag,
        pbody: true
      }
    ],
    __dangerouslyDisableSanitizersByTagID: {
      'gtmHead': ['innerHTML'],
      'gtmBody': ['innerHTML']
    }
  }
}
```

### コンパイル後のソース
```html:*.html
<head>...
<script data-n-head="ssr" data-hid="gtmHead">(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>
...</head>
<body>
  <noscript data-n-head="ssr" data-hid="gtmBody" data-pbody="true"><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
...
```


## 解説（補足）


#### `script:[]`  
`<script>`タグを`<head></head>`内に埋め込めます。
共通で読み込みたいsrcファイルなどを指定（[公式](https://ja.nuxtjs.org/faq/)）する際にも使えますが、
`innerHTML`というキーに文字列を渡すと、`<script>~<script>`内に文字列を挿入できます。
> https://vue-meta.nuxtjs.org/api/#script


#### `noscript:[]`
`<noscript>`タグを`<body></body>`内に埋め込めます。
オプションで、挿入位置を指定できます。
`<body>`直後の場合は`pbody:true`、`</body>`直前の場合は`body:true`を指定します。
> 
https://vue-meta.nuxtjs.org/api/#noscript
https://vue-meta.nuxtjs.org/api/#noscript-text


#### `__dangerouslyDisableSanitizersByTagID:`

##### ①inject許可
vue.jsで構築されたアプリケーションは、DOM攻撃（XSS等）の危険性があるので、
管理外の`<head>`や`<body>`へのタグの追加・変更に堅いです。
ここで指定されたキーバリューは、例外となりinjectを許可されます。

> https://vue-meta.nuxtjs.org/api/#dangerouslydisablesanitizersbytagid

※注：同様に`__dangerouslyDisableSanitizers`というのがありますが、
こちらは指定したタグ全般に対し例外適用されるので、ID指定のほうを使いましょう。

##### ②escapeされない
また、ここで指定された値はrawData扱いされるため、
htmlに出力される際escapeされないというところもポイントとなります。
> https://vue-meta.nuxtjs.org/api/#add-other-raw-data



やってみていただくとわかるのですが、こちらのオプションをつけずに出力した場合、
`<body>`側に埋め込んだ`<iframe>`タグは下記のように`&lt;iframe `と出力されてしまいます。
（これを解決するのに、[DOM Parser](https://qiita.com/mafuyuk/items/4a3afbad46b04f92da29)なども試したが駄目で、ハマりました・・）

```html:*.html
<body>
  <noscript data-n-head="ssr" data-hid="gtmBody" data-pbody="true">&lt;iframe src=&quot;https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX&quot; height=&quot;0&quot; width=&quot;0&quot; style=&quot;display:none;visibility:hidden&quot;&gt;&lt;/iframe&gt;</noscript>
```

####  その他注意
vue-meta公式だとIDに設定するキーの名前が`vmid`になっているのですが、
nuxtの場合は`hid`にしてあげないと効きません。（こちらも地味にハマった、、）


## おわり

シンプルな実装で、計測も無事確認できたので、同じような状況の方はためしてみてください🎉
（ちなみにこのスクリプト挿入はGTMタグ以外にも応用可能です）