即時のナビゲーション エクスペリエンス

従来のプリフェッチ手法を Service Worker で補完する。

サイトでタスクを実行するには、通常、いくつかのステップが必要です。たとえば、e コマース ウェブサイトで商品を購入するには、商品の検索、結果のリストからの商品の選択、カートへの商品の追加、決済によるオペレーションの完了などがあります。

技術的に言うと、別のページを移動するにはナビゲーション リクエストを行います。原則として、ナビゲーション リクエストの HTML レスポンスをキャッシュに保存するために、有効期間の長い Cache-Control ヘッダーは使用しません。HTML と後続のネットワーク リクエストのチェーンが(妥当な)新しいものになるように、通常はネットワーク経由で Cache-Control: no-cache を使って処理します。 残念なことに、ユーザーが新しいページに移動するたびにネットワークにアクセスしなければならないため、それぞれの移動が遅くなり、少なくとも、確実には遅くなります。

リクエストを高速化するには、ユーザーのアクションが予測できる場合に、これらのページとアセットを事前にリクエストして、ユーザーがリンクをクリックするまでの短い期間キャッシュに保持します。これはプリフェッチと呼ばれ、通常、プリフェッチするリソースを示す <link rel="prefetch"> タグをページに追加することで実装されます。

このガイドでは、従来のプリフェッチ手法を補完する手段として Service Worker を使用するさまざまな方法を説明します。

本番環境のケース

MercadoLibre は、ラテンアメリカ最大の e コマースサイトです。ナビゲーションを高速化するために、フローの一部に <link rel="prefetch"> タグを動的に挿入します。たとえば、リスティング ページでは、ユーザーがリスティングの一番下までスクロールするとすぐに次の検索結果ページを取得します。

MercadoLibre のリスティング ページ 1 と 2 のスクリーンショットと、その両方を接続するリンク プリフェッチ タグのスクリーンショット。

プリフェッチされたファイルは、「最も低い」優先度でリクエストされ、リソースがキャッシュ可能かどうかに応じて、HTTP キャッシュまたはメモリ キャッシュに一定期間保存されます。たとえば、Chrome 85 では、この値は 5 分です。リソースは 5 分間保持され、その後はリソースに対する通常の Cache-Control ルールが適用されます。

Service Worker のキャッシュを使用すると、プリフェッチ リソースの有効期間を 5 分よりも長くすることができます。

たとえば、イタリアのスポーツ ポータルである Virgilio Sport は、Service Worker を使用して、ホームページで最も人気のある投稿をプリフェッチしています。また、2G 接続を使用しているユーザーのプリフェッチを回避するため、Network Information API が使用されます。

Virgilio Sport のロゴ。

その結果、Virgilio Sport は 3 週間以上のモニタリングで記事へのナビゲーションの読み込み時間が 78% 改善し、記事の表示回数が 45% 増加しました。

Virgilio Sport のホームページと記事ページのスクリーンショット。プリフェッチ後の影響の指標が表示されています。

Workbox を使用したプレキャッシュの実装

次のセクションでは Workbox を使用して、このタスクを完全に Service Worker に委任することで、Service Worker にさまざまなキャッシュ手法を実装します。この手法は、<link rel="prefetch"> を補完したり、代替として使用したりします。

1. 静的ページとページ サブリソースを事前キャッシュに保存する

プレキャッシュとは、インストール中にファイルをキャッシュに保存する Service Worker の機能です。

次のケースでは、プリフェッチと同様の目的、すなわちナビゲーションの高速化を実現するために、プリフェッチが使用されます。

静的ページの事前キャッシュ

ビルド時に生成されるページ(about.htmlcontact.html など)や、完全に静的なサイトの場合、サイトのドキュメントをプレキャッシュ リストに追加するだけで、ユーザーがアクセスするたびにキャッシュで利用できるようになります。

workbox.precaching.precacheAndRoute([
  {url: '/about.html', revision: 'abcd1234'},
  // ... other entries ...
]);

ページ サブリソースの事前キャッシュ

サイトのさまざまなセクションで使用される可能性がある静的アセット(JavaScript、CSS など)のプリフェッチは一般的なベスト プラクティスであり、プリフェッチのシナリオをさらに強化できます。

e コマースサイトでのナビゲーションを高速化するには、リスティング ページで <link rel="prefetch"> タグを使用して、リスティング ページの最初の数個の商品について、商品の詳細ページをプリフェッチします。商品ページのサブリソースをすでにキャッシュに保存している場合は、ナビゲーションをさらに高速化できます。

これを実装する手順は次のとおりです。

  • <link rel="prefetch"> タグをページに追加します。
 <link rel="prefetch" href="/phones/smartphone-5x.html" as="document">
  • ページのサブリソースを Service Worker のプレキャッシュ リストに追加します。
workbox.precaching.precacheAndRoute([
  '/styles/product-page.ac29.css',
  // ... other entries ...
]);

2. プリフェッチ リソースの有効期間を延長する

前述のように、<link rel="prefetch"> はリソースをフェッチして HTTP キャッシュに一定期間保持します。その後、リソースに対する Cache-Control ルールが適用されます。Chrome 85 では、この値は 5 分です。

Service Worker を使用すると、プリフェッチ ページの有効期間を延ばせるだけでなく、それらのリソースをオフラインで使用可能になるというメリットもあります。

前の例では、商品ページのプリフェッチに使用した <link rel="prefetch"> を、Workbox ランタイム キャッシュ戦略で補完できます。

これを実装する手順は次のとおりです。

  • <link rel="prefetch"> タグをページに追加します。
 <link rel="prefetch" href="/phones/smartphone-5x.html" as="document">
  • 次のタイプのリクエストに対して、Service Worker にランタイム キャッシュ戦略を実装します。
new workbox.strategies.StaleWhileRevalidate({
  cacheName: 'document-cache',
  plugins: [
    new workbox.expiration.Plugin({
      maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
    }),
  ],
});

ここでは、stale-while-revalidate 戦略を使用することにしました。この方法では、キャッシュとネットワークの両方から並行してページをリクエストできます。レスポンスは、可能であればキャッシュから、それ以外の場合はネットワークから送信されます。キャッシュは、リクエストが成功するたびにネットワーク レスポンスによって常に最新の状態に保たれます。

3. プリフェッチを Service Worker に委任する

ほとんどの場合、最善の方法は <link rel="prefetch"> を使用することです。タグは、プリフェッチを可能な限り効率的に行うためのリソースヒントです。

ただし、このタスクを完全に Service Worker に委任した方がよい場合もあります。たとえば、クライアントサイドでレンダリングされた商品リスティング ページで最初の数個の商品をプリフェッチするには、API レスポンスに基づいて、複数の <link rel="prefetch"> タグをページに動的に挿入する必要があります。これにより、ページのメインスレッドが一時的に消費され、実装が困難になります。

このような場合は、「ページ間通信戦略」を使用して、完全にプリフェッチするタスクを Service Worker に委任します。このタイプの通信は、worker.postMessage() を使用して実現できます。

Service Worker との双方向通信を行うページのアイコン。

Workbox Window パッケージは、このタイプの通信を簡素化し、基になる呼び出しの詳細の多くを抽象化します。

ワークボックス ウィンドウを使用したプリフェッチは、次の方法で実装できます。

  • ページで: Service Worker を呼び出し、メッセージの種類とプリフェッチする URL のリストを渡します。
const wb = new Workbox('/sw.js');
wb.register();

const prefetchResponse = await wb.messageSW({type: 'PREFETCH_URLS', urls: […]});
  • Service Worker で、プリフェッチする URL ごとに fetch() リクエストを発行するメッセージ ハンドラを実装します。
addEventListener('message', (event) => {
  if (event.data.type === 'PREFETCH_URLS') {
    // Fetch URLs and store them in the cache
  }
});