Google Optimizeの導入方法(低レイテンシでページフリックを軽減する方法)

職場でA/Bテストのツールの導入をすることになり、いろいろ調べてみたところ、**Google Optimize**を導入することにしました。

以前、Google Analytics(以下GA)のウェブテストを使っていたのですが、

![ウェブテスト_-_アナリティクス_360 (1).png](https://qiita-image-store.s3.amazonaws.com/0/237077/c7fc7da6-c22a-1dd6-a0d5-2ad2d34adb2e.png)

の通り、今後使えなく可能性もありそうということで、Optimizeを調べてみました。

# Optimizeのメリット・GAのウェブテストとの違い

いろいろ知らべてみたところ、GAのウェブテストとの大きな違い・メリットは以下の３点だと思います。

- GAではテスト対象となるページを1ページしか指定できなかったため、複数のページにまたがるテストをやる場合は、結構面倒なことをしなければならなかったが、Optimizeではテスト対象ページのURLをパターンマッチでまとめて指定できる。
- ページフリック（後述）を軽減するための手段が提供されている。ページの表示が数ms遅れるのを許容できるのであれば、テスト対象ユーザーとして振り分けられたあとに、テストページにリダイレクトさせる手間がなくなる。
- テストをブラウザ上で作成できる。例えば指定したページのとあるボタンの色のパターンテストをしたい場合は、Chromeの拡張機能を利用して、直接ブラウザでページ上のパーツをクリックして、画像の色などを指定するなどしてパターンを作成できる。CSSやJSも直接追加できる。

## ページフリックとは

テスト対象ページにユーザーが訪れたときに、オリジナルページに対して背景の変更やCSSの変更などのDOM操作が走り、見えている画面に変更が加えられ、一瞬画面がチラッとする現象のこと。

例えば、ページのヘッダーの背景の色がオレンジのサイトで、その色を白などのいくつかの色でテストしたい場合、一旦オリジナルのオレンジで描画されるが、テストを実行するJS実行後にパッと白いヘッダーに切り替わる（ページフリックが起こる）。

これだとページに訪れたユーザーには「なんで一瞬オレンジのヘッダーが表示されたんだろ」という違和感を与えてしまう。そのため、ユーザーのテストパターンへの振り分けが行われたあとに、テストパターンのURLへリダイレクトさせる手法が通例でした。

Optimizeではこの現象を最小限におさえるための[スニペット](https://support.google.com/360suite/optimize/answer/7359264)(javascriptのちょっとしたプログラム)が配布されています。

# Optimizeの導入方法

本記事では３つの導入方法をご紹介したいと思います。
1. タグマネージャーを使った導入
2. ユニーバサルアナリティクス(analytics.js)を使っている場合
3. タグマネージャを使いつつも、Optimizeのタグを直接サイトに貼って利用する場合

今回私が選んだのは3番目のやり方なのですが、１と２のやり方も理解しておく必要があるので
１と２についても簡単にご紹介致します。

## タグマネージャを使った導入
Optimizeの最も簡単な導入方法はGoogleタグマネージャを使ったやり方です。
ぐぐると多くのサイトでこのやり方が紹介されていました。
なので、ここでは詳細な説明は省きますが、手順は大体以下のとおりです。

1. Optimzeのコンテナを作成する
2. タグマネージャでOptimizeのタグを作成し、上記と紐付ける
3. さらにGoogle Analyticsと紐付け、着火のタイミングを指定する。
4. タグマネージャのタグが貼り付けられていれば、特に新たなタグを貼ったりする必要はありません。

※ただし、ページフリックを軽減するためのスニペットを使う場合、レイテンシが大きくなりユーザビリティが低下する可能性があります。

## ユニーバサルアナリティクス(analytics.js)を使っている場合
こちらはタグマネージャを使っておらず、analytics.jsを使っている場合の例です。一応基本なので簡単にまとめます。

```html
<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
</script>
```

おそらくこういうタグがあなたのページに貼られているはずです。
そこに次の三行を追加します。

```js
ga('create', 'UA-XXXXXXX-X', 'auto');
ga('require', 'GTM-XXXXXX');
ga('send', 'pageview');
```

`UA-XXX`のところは紐付けたいGoogle AnalyticsのプロパティID。
`GTM-XXXX`は作成したOptimizeのコンテナIDです。これを挿入することで両者が連携します。

結果としてはこのようになります。

```html
<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
 
  ga('create', 'UA-XXXXXXX-X', 'auto');
  ga('require', 'GTM-XXXXXX');
  ga('send', 'pageview');
 </script>
```

詳しくは[Googleの公式レファレンス](https://support.google.com/360suite/optimize/answer/6262084?hl=ja&ref_topic=6197443)をご参照ください。

## タグマネージャを使いつつも、Optimizeのタグを直接サイトに貼って利用する場合(ページフリック軽減スニペットを低レイテンシで使いたい場合)

見出しが長いですが、事情はちょっと複雑です。
これがやりたかったのですが、ぐぐっても、公式レファレンス見てもわかりにくかった。
まずページフリック軽減スニペットですが、こちらになります。

```html
<!-- Page-hiding snippet (recommended)  -->
<style>.async-hide { opacity: 0 !important} </style>
<script>(function(a,s,y,n,c,h,i,d,e){s.className+=' '+y;h.start=1*new Date;
h.end=i=function(){s.className=s.className.replace(RegExp(' ?'+y),'')};
(a[n]=a[n]||[]).hide=h;setTimeout(function(){i();h.end=null},c);h.timeout=c;
})(window,document.documentElement,'async-hide','dataLayer',4000,
{'GTM-XXXXXX':true});</script>
```

これは[こちら](https://support.google.com/360suite/optimize/answer/7359264)で配布されているとおりです。

ちょっと分かりにくいですが、`.async-hide`というクラスを持つDOM要素を強制的に透明にしています。Optimizeがユーザーの振り分けが完了して、テストパターンの実行が完了すると、透明化が解除されます。そのため、ブラウザが実行する以下の3ステップの間は画面が真っ白になっているため、上述するようなページフリックが極力抑えられます。

1. Optimizeのロード
2. Optimzeがユーザーをどのテストパターンに振り分けるかを決定
3. テストパターンDOMへの切り替え

そのためユーザーは違和感なく、テストパターンのページを閲覧し始められます。ただし、ネットワークが細かったりすると、上記の3工程に時間がかかり、真っ白な画面がずっと表示されてしまいます。そのため、デフォルトのスニペットでは４秒以内に完了しなかった場合は、テストをスキップし、オリジナルパターンがそのまま表示されるようになっています。しきい値は任意で調整が可能です。

これは非常に便利なのですが、タグマネージャ上でOptimizeを利用すると、上記の3工程の前に、

1. タグマネージャのロード
2. タグマネージャに紐付けられているanalytics.jsのロード
3. それに紐付けられているOptimizeのタスクの着火

という3つの工程がさらに必要になってしまい、真っ白な画面の状態が長くなる可能性が高くなります。つまりレイテンシの肥大化問題です。これを解決するのが今回ご紹介したい見出しのやり方です。具体的には下記になります。

1. `<meta charset>`配下にdataLayerなどの変数を置く。
2. ページフリック軽減スニペットを貼る
3. analytics.jsのロードとそれに連携させるOptimizeのコンテナIDの紐づけ
4. 通常のタグマネタグ

より具体的に説明していきます。

まず`<meta charset>`タグの下からすかさず上記の４つの関連タグを入れていくのがポイントです。通常であればページのレンダリングに必要のないものなので、ブラウザが実行する優先順位を低くするために、`DOMContentLoaded`が終わってからとか、asyncで実行させたりします。

ところがウェブテストの場合は、テストの振り分けが終わらないとページをレンダリングできないので、それを最優先にするためにcharsetの下につける感じになります。

そして3はまったく新しい要素です。タグマネージャーを使ってGAを利用しているにも関わらず、ここでは直接analytics.jsを呼び出します。

```html
<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
  ga('create', 'UA-XXXXXXX-X', 'auto');
  ga('require', 'GTM-YYYYYYY');
</script>
```

最後の"ga()"のところでGAとOptimizeを連携させます。「ユニーバサルアナリティクス(analytics.js)を使っている場合」でご紹介したとおりです。

そして最後に４、いつものタグマネのタグをそのまま貼っておきます。最終型としては以下になります。

```html
<meta charset="UTF-8">
<!-- Optimize Page Hiding Snippet -->
  <style>.async-hide { opacity: 0 !important} </style>
  <script>(function(a,s,y,n,c,h,i,d,e){s.className+=' '+y;h.start=1*new Date;
  h.end=i=function(){s.className=s.className.replace(RegExp(' ?'+y),'')};
  (a[n]=a[n]||[]).hide=h;setTimeout(function(){i();h.end=null},c);h.timeout=c;
  })(window,document.documentElement,'async-hide','dataLayer',4000,
  {'GTM-XXXXXX':true});</script>
 
 
<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
  ga('create', 'UA-123456-1', 'auto');
  ga('require', 'GTM-XXXXXX');
</script>
 
 
<!-- 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-XXXX');</script>
<!-- end Google Tag Manager -->
```

上記の例は[こちらのサイト](https://www.lunametrics.com/blog/2017/03/02/install-google-optimize/)からほとんど丸ごと拝借しております。

# テストが走っていないときの運用

テストが走っているときは上記の状態で問題ありません。ただし、テストがないときにまでOptimzeに問い合わせてページフリックを軽減するために、無駄に真っ白な画面を表示させておくのはあまり好ましいとは言えません。直帰率の上昇などにつながります。

なのでテストが走っていないときにはページフリック軽減スニペットを外したり、タグ真似の位置をCSSのロードの下など、レンダリングをブロックしないところに持ってきたりする必要があると思います。

[具体的なテストの作成方法についてメモしました](https://qiita.com/takeshi_nozawa/items/585c2d4efd2b59f7b52c)。ご参考になれば幸いです。