従来のプリフェッチ手法を Service Worker で補完する。
サイトでタスクを実行するには、通常、いくつかのステップが必要です。たとえば、e コマース ウェブサイトで商品を購入するには、商品の検索、結果のリストからの商品の選択、カートへの商品の追加、決済によるオペレーションの完了などがあります。
技術的に言うと、別のページを移動するにはナビゲーション リクエストを行います。原則として、ナビゲーション リクエストの HTML レスポンスをキャッシュに保存するために、有効期間の長い Cache-Control
ヘッダーは使用しません。HTML と後続のネットワーク リクエストのチェーンが(合理的に)新しいものになるように、通常はネットワーク経由で Cache-Control: no-cache
を使って処理します。
残念ながら、ユーザーが新しいページに移動するたびにネットワークにアクセスしなければならなくなると、それぞれの移動が遅くなり、少なくとも、確実には遅くなります。
リクエストを高速化するには、ユーザーのアクションが予測できる場合に、これらのページとアセットを事前にリクエストして、ユーザーがリンクをクリックするまでの短い期間キャッシュに保持します。これはprefetchingと呼ばれ、通常、プリフェッチするリソースを示す <link rel="prefetch">
タグをページに追加することで実装されます。
このガイドでは、従来のプリフェッチ手法を補完する手段として Service Worker を使用するさまざまな方法を説明します。
本番環境のケース
MercadoLibre は、ラテンアメリカ最大の e コマースサイトです。ナビゲーションを高速化するために、フローの一部に <link rel="prefetch">
タグを動的に挿入します。たとえば、リスティング ページでは、ユーザーがリスティングの一番下までスクロールするとすぐに次の検索結果ページを取得します。
プリフェッチされたファイルは「最小値」優先度が高まり、リソースがキャッシュ可能かどうかに応じて、HTTP キャッシュまたはメモリ キャッシュに一定期間保存されます。たとえば、Chrome 85 では、この値は 5 分です。リソースは 5 分間保持され、その後はリソースに対する通常の Cache-Control
ルールが適用されます。
Service Worker のキャッシュを使用すると、プリフェッチ リソースの有効期間を 5 分よりも長くすることができます。
たとえば、イタリアのスポーツ ポータルである Virgilio Sport は、Service Worker を使用して、ホームページで最も人気のある投稿をプリフェッチしています。また、2G 接続を使用しているユーザーのプリフェッチを回避するため、Network Information API が使用されます。
その結果、Virgilio Sport は 3 週間以上のモニタリングで記事へのナビゲーションの読み込み時間が 78% 改善し、記事の表示回数が 45% 増加しました。
Workbox を使用したプレキャッシュの実装
次のセクションでは Workbox を使用して、このタスクを完全に Service Worker に委任することで、Service Worker にさまざまなキャッシュ手法を実装します。この手法は、<link rel="prefetch">
を補完したり、代替として使用したりします。
1. 静的ページとページ サブリソースを事前キャッシュに保存する
プレキャッシュとは、インストール中にファイルをキャッシュに保存する Service Worker の機能です。
次のケースでは、プリフェッチと同様の目的、すなわちナビゲーションの高速化を実現するために、プリフェッチが使用されます。
静的ページの事前キャッシュ
ビルド時に生成されるページ(about.html
、contact.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() を使用して実現できます。
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
}
});