すでに知られているように、Webページの表示速度は重要です。利用者はいつでもどこでも素早くページが表示されて欲しいと思うでしょう。Core Web Vitalsの指標でも表されているように、表示速度は一時的に速いだけでなく、安定していることが求められます。

本記事では、安定した表示速度を実現する手段の一つとして考えられるPrerenderingをオリジン・トライアルで試してみた結果をご紹介します。

Prerenderingとは

Prerenderingは次に表示されると思われるページを事前にレンダリングします。レンダリングが完了している場合には、利用者がそのページに遷移するリンクをクリックした際に即座にページが表示されます。

Prerenderingを実現するための手段にはいくつかの方法がありますが、本記事では新たに追加されたSpeculation Rulesを用いてのPrerenderingを紹介します。

ヘッドラインページの「続きを読む」ボタンを押すとPrerenderingされた記事詳細ページが表示されます。遷移先のページはほぼ遅延なくコンテンツが表示されます。

Prerendering実装前に注意すること

Prerenderingを実装する前に注意することがいくつかあります。一つはPrerendering中にいくつかの処理を実行しないようにする必要があります。例えば、ページビューや表示パフォーマンス指標は、1ページ表示で2回収集されるべきではありません。それらは以下のようにして、実際に表示された時(アクティベーション)より後に処理を遅延させます。

Note: なお、現時点(2022年8月)での実装においてはCross-Origin iframeで指定されたリソースへのリクエストはPrerendering時に実行されません。そのため、多くの広告は特に対応せず問題ないと思われます。また、表示に関連する処理もPrerendering時には実行されません。


function waitForPageActivation() {
  return new Promise((resolve) => {
    // document.prerenderingがある場合はPrerenderingされているページ
    if (document.prerendering) {
      // 実際に表示された場合にはprerenderingchangeイベントが発火する
      document.addEventListener('prerenderingchange', () => resolve());
    } else {
      resolve();
    }
  });
}

waitForPageActivation().then(sendPageView);
waitForPageActivation().then(addPerformanceListener);

次に、Largest Contentful Paint (LCP)をRUMとして計測する場合は、開始時間をアクティベートされたタイミングからの秒数に変換する必要があります。そうしないと、LCPが悪化したかのようになり、正確な値が取得できなくなってしまいます。LCPの計算を以下のように変更します。


import { getLCP } from 'web-vitals';

getLCP(({ delta }) => {
  const timing = performance.getEntriesByType('navigation')[0];
  // activationStart は prerenderingchange イベントが発火したタイミングから取得できる(それ以前は取得できない)
  const activationStart = timing.activationStart ? timing.activationStart : 0;
  const value = delta - activationStart;
});

Note: 上記の例ではweb-vitals.jsのstableバージョンを利用していますが、Prerendering 計測対応したものを最新の@next バージョンで実験的に公開しています。

さらに、どの遷移フローがPrerenderingに適しているか事前に調査しておくと良いでしょう。Prerenderingはできる限りそのページへ遷移する可能性が高い時に設定すると効果的です。利用者が次のページに遷移することなく頻繁に離脱してしまった場合には、ネットワークやバッテリーを浪費してしまう可能性があります。

Quicklinkを使ったPrerenderingの実装

今回のオリジントライアルでは、Newsサイトのヘッドラインページが表示された時に、詳細ページをPrerenderingしてみました。quicklinkというライブラリで提供されているprerenderメソッドを利用しています。


<script type=”module”>
  import { prerender } from ‘https://.../quicklink.mjs’;

  window.addEventListener('load', () => {
    const match = location.pathname.match(/\\/(.+)\\/hl\\/([0-9a-z-_]+)/);
    if (!match) return;

    const [_, dirname, entryId] = match;
    prerender(`/${dirname}/${entryId}/`);
  });
</script>

prerenderメソッドを実行すると、以下のようなSpeculation Rulesが挿入されます。このルールを直接記述することでもPrerenderingを実行できますが、quicklinkを使うと読みやすく簡単に設定できるので時間短縮に役立ちました。


<script type="speculationrules">
  {"prerender":[{"source": "list","urls": ["/entry/12345/"]}]}
</script>

Prerenderingのヒット率を計算する

今回のトライアルでは実際にどれくらいPrerenderingされたページが表示できたかを把握するために、ヒット率を取得しました。ヒット率を計算するためには以下のようにPrerenderingされたタイミングとアクティベーションされたタイミングでログを送信します。


if (document.prerendering) {
  // Timing to send prerendering

  document.addEventListener('prerenderingchange', () => {
    // Timing to send activation
  });
}

Prerenderingの結果

Prerenderingを実際に1週間テストした結果、LCPの中央値が0.68秒から0.31秒へと約半分へと改善しました。さらに、ばらつきが縮まり、95パーセンタイルの数値が2.2秒から1.2秒へと改善しました。これは多くの利用者が1秒前後でコンテンツを取得できているというより好ましい状態でもあります。

Prerenderingのヒット率は約30%で、少なくはありませんが、改善できるとよりよさそうです。無駄な端末資源の消費を防ぐため、適切な利用やより効率的な推測が求められるかもしれません。

Prerendering quicklink, -1.0s P95 LCP, Prerendering P5 138s P50 310ms P95 1206ms, Non-prerendering P5 216ms P50 680ms P95 2200ms, Ameba