Prerender APIでWebサイトを高速化

[転職ナビ](https://job.j-sen.jp/?jl_ref=qiita)にResource Hints API のPrerender API(以降Prerender)を用いてページの事前読み込みする機能を導入しました。
導入にだいぶ苦労しましたが、速くなったのでまとめてみました。

## Prerenderとは
遷移する前に特定のページをレンダリングしておくことで、高速なページ表示を可能とする機能です。
最近だと[日経電子版で導入されていた記事](https://logmi.jp/283703)が出ていたりと、PWAを始めとする高速化の話題とあわせてよく聞くようになってきたと思います。

## Prerenderを用いた高速化のメリット

* 事前にレンダリングを開始するので高速にページ表示ができる
* 比較的ライトに高速化対応の導入ができる

## Prerenderを用いた高速化のデメリット

* ページで一つしかプリレンダリングするページを指定できないため、使用が難しい
* プリレンダリングではJavaScriptが発火されるので、ユーザーが実際に表示していないページの状態管理が複雑になる(アドや分析用のタグなど)

## 対応ブラウザ
2018年4月時点では

* Google Chrome(PCとSP)
* IE11


の2つです。

**参考**
https://caniuse.com/#search=prerender

## 導入方法
基本的にはこれだけでOKです。

```html

<link rel="prerender" href="https://hoge.jp">
```

ただし、後述しますが保守コスト等を考慮して、併せて

* 動的にlinkタグを挿入
* 広告用等のJavaScriptの発火を制御

これら２つの機能を実装しました。

## 導入のポイント
### 動的にlinkタグを挿入
Prerenderの注意点としてPrerenderの指定は1ページにつき一つしかできません。
JavaScriptで動的に追加したり削除しても、最初の指定した1ページしかプリレンダリングできないです。
その為、実装方針としては

* 各ページでのプリレンダリングを決め打ちで指定をする
* ユーザーが初めにhoverしたURL(リンク)をPrerender指定する

の2通りが一般的です。
前者だと各ページ毎に個別最適しなければいけないこともあり、後者の方針を選択しました。
ただし、この方針だとユーザーがマウスで上を(クリックを目的としていないのに)hoverしただけでも、プリレンダリングしてしまうため、hoverしてから100ms経った場合にPrerender指定するようにしました。
ユーザーがhoverしてからclickまで200ms〜300ms間があるようなので、その分早くユーザーが画面を表示できることを狙いました。
えいやで実装した側面もあるので、この秒数に関しては今後もチューニングする予定です。

**参考**
http://instantclick.io/

### 広告用等のJavaScriptの発火を制御

#### Page Visibility API
プリレンダリングは、実質的には別タブで開いている状態です。
つまり、ユーザーが実際に表示しなくても、効果測定や広告用のJavaScriptが発火してしまいます。
今回はPageVisivilityAPIを使用して、プリレンダリング時にはこれらのJavaScriptを発火しないようにしました。
また、サンプルコードには記載していませんが、プリレンダリング時から状態が変更された際に動的に広告用のコードを初期化するように書き換えました。

```javascript

var displayedFlg = false;
if(document.visibilityState === 'prerender') {
  document.addEventListener('visibilityChange', function() {// ----※1
    if(displayedFlg || document.visibilityState === 'hidden') {// ----※2
      return;
    }
    initAd();//----※3
    displayedFlg = true;
  });
} else {
  initAd();
}
```

##### 補足
※1.Chrome、IEではvisibilityChangeイベントにリスナーを登録できなかったので、上記のコードとは別に`webkitvisibilitychange`と`msvisibilitychange`イベントにも登録しました。
※1.`document.hidden`や`webkitHidden`が`undefined`かどうかでイベントの種類(`visibilitychange`、`msvisibilitychange`)を区別をしたかったのですが、できなかったです。
※2.Chromeではプリレンダリングが実際に画面に表示される際に、`document.visibilityState`の値が`prerender`→`hidden`→`visibile`と変更されます。
※3.Universal Analytics等一部のサービスは対応しているという記事もみかけたのですが、よくわからなかったです。したがって、ABテストツールやアナリティクスのスクリプト全て含めました。



**参考**
https://developer.mozilla.org/ja/docs/Web/Guide/User_experience/Using_the_Page_Visibility_API

#### ブラウザの判別
上記で書きましたが、hover時にプリレンダリングするようにしたので、Prerenderに対応しているPC向けブラウザのみにしました。
ただし、全てのaタグにイベント登録しなければならないので、対応ブラウザを判別してプリレンダリングするようにしています。

```javascript

canPrerender() {
  const ua = window.navigator.userAgent.toLowerCase();
  return ua.indexOf('trident') !== -1 || (ua.indexOf('chrome') !== -1 && ua.indexOf('edge') === -1);
}
```

**参考**
https://qiita.com/sakuraya/items/33f93e19438d0694a91d

## 確認方法

### Google Chrome
実際にプリレンダリングされているかは「その他のツール > タスクマネージャー」から確認できます。
尚、devtoolsを開いている状態ではプリレンダリングされないです。

![prerendering_chrome_qiita](https://qiita-image-store.s3.amazonaws.com/0/146426/c55d225e-0b5b-6552-b503-6cf2b9cd57e4.png)


### IE
aタグをhoverした際に左下に「◯◯の事前読み込み」と表示されます。
ただ、事前読み込みと表示されても開発者ツールを開いた状態だと、プリレンダリングされなかったっぽい。。。

![prerendering_ie_qiita.png](https://qiita-image-store.s3.amazonaws.com/0/146426/1e481254-8916-1db3-07a6-c45d1ab55577.png)

## 参考
https://blog.jxck.io/entries/2016-02-11/resource-hints.html
https://qiita.com/ryo_hisano/items/e525616e5026b015aafe
http://subtech.g.hatena.ne.jp/mayuki/20131209/1386606563

## さいごに
最後まで読んで下さいましてありがとうございました。
効果測定まわりにも言及したかったのですが、言及するとしたら、「visibilityStateの値がprerenderからvisibleに変更された時間を計測用DBやツールに送信する」とかですかね？
ただ、実際のレンダリングはvisibilityStateの値がprerenderになるまでにも時間は少し関わるわけでして。。。
導入で一番苦労したのは効果測定が難しくて(そもそも動作が不安定？なのもあり)、外部にドヤしにくかった点かもしれません(笑)
Service Workerを使用できるブラウザがどんどん増えてきたので、次はPWA技術の導入にトライしてより良い体験をユーザーに届けたいと思います！