プロンプトとコンテキスト
対象のサイトでは、オフラインフォールバックと共有キャッシュのために Service Worker を使ってナビゲーションリクエストをインターセプトしています。初回のナビゲーションでは、ネットワークリクエストが開始される前にワーカーの起動を待機します。重複リクエスト、古いキャッシュの書き込み、レスポンスの競合を防ぎつつ、非対応ブラウザにも配慮した Navigation Preload を設計してください。
面接官がテストしていること
Navigation Preload は、Service Worker の起動中にブラウザが開始するナビゲーションリクエストであり、プリキャッシュや任意のリソース向けの link rel=preload とは異なります。activate 中の有効化、fetch における preloadResponse の読み取り、ネットワーク対キャッシュのポリシー、Service-Worker-Navigation-Preload ヘッダー、タイムアウトやエラー時のフォールバック、そして計測方法を網羅してください。
最初に確認すべき明確化のための質問
ページとキャッシュの対象
ナビゲーションが SSR HTML、SPA シェル、オフラインページのどれを返すのか、どのパスがキャッシュ可能か、ユーザー固有のデータが共有キャッシュをバイパスする必要があるかを確認します。パーソナライズされたレスポンスをパブリックキャッシュに書き込んではいけません。
ライフサイクルとブラウザサポート
登録、更新、制御スコープ、および navigationPreload のターゲットブラウザのサポート状況を確認します。制御されていない初回ナビゲーションは、現在のワーカーを通過するとは想定できません。
ネットワークと一貫性のポリシー
Network-First、Cache-First、または Stale-While-Revalidate の動作を選択し、オフラインレスポンスを定義します。サーバーがキャッシュキーを誤って変更することなく preload ヘッダーをどのように読み取るかを決定します。
30秒の回答フレームワーク
「Service Worker の activate イベントで機能検知を行い、Navigation Preload を有効化します。ブラウザはワーカーの起動中にナビゲーションリクエストを開始します。fetch ハンドラーはまず event.preloadResponse を await し、利用可能なプリロードレスポンスが存在しない場合にのみキャッシュを確認するか fetch を呼び出します。URL、同一性、キャッシュポリシーが一致した場合にのみレスポンスを受け入れます。ロールアウトの前後で、サポート状況、起動時間、TTFB、キャッシュヒット率、重複リクエスト、フォールバックエラーを比較します。」
ステップバイステップの詳細な回答
ステップ 1: activate 中に有効化する
registration.navigationPreload を確認した後、activate の waitUntil 内で enable() を呼び出し、新しいワーカーの準備が整ったとみなされる前に設定を完了させます。setHeaderValue() でコンテキストを追加できますが、その値にプライベートな状態やキャッシュ不可能な状態を含めてはいけません。
ステップ 2: fetch で preloadResponse を消費する
ナビゲーションリクエストに対してのみ event.preloadResponse を読み取ります。これは Response または undefined に解決される可能性があります。有効なプリロードレスポンスを優先し、それ以外の場合はキャッシュまたは通常の fetch ポリシーに従います。プリロードの Promise が利用可能な結果を生成したかどうかを判断する前に、2回目のネットワークリクエストを開始しないでください。
ステップ 3: キャッシュと同一性の境界を定義する
アイデンティティ、地理情報、または実験データを含む SSR HTML は、既存のキャッシュキーとレスポンスヘッダーを尊重する必要があります。これを無条件に Cache Storage に格納してはいけません。静的シェルは Cache-First になる場合があり、パーソナライズされたページは Network-First になる場合があり、Cookie、Authorization、Vary の動作を明示的にテストします。
ステップ 4: ヘッダーとサーバールーティングを処理する
サーバーは Service-Worker-Navigation-Preload を検査して並列リクエストを認識し、負荷の高い処理をスキップしたり専用キャッシュを使用したりできます。プロキシや CDN では、ユーザー間でのキャッシュ共有が発生しないよう、そのヘッダーの転送、無視、または Vary に関する明示的なルールが必要です。
ステップ 5: 競合、タイムアウト、エラーを処理する
プリロードが失敗したか、タイムアウトしたか、または不適切なステータスを返した場合は、ポリシーに従ってキャッシュまたは通常の fetch にフォールバックします。Response body は通常1回しか消費できません。2つのコンシューマーが必要な場合は clone() を使用し、キャンセルとタイムアウトの境界を設定します。ネットワークエラー後はオフラインページを試行しますが、実際のサーバーエラーを隠蔽してはいけません。
ステップ 6: 安全に縮退・更新する
非対応ブラウザは通常の Service Worker の fetch を継続し、Service Worker をサポートしていないブラウザはネットワークを使用します。ワーカーの更新中は古いキャッシュプロトコルを保持し、新しいワーカーが使用可能になるまで削除を延期することで、アクティベーション時の障害を防ぎます。
ステップ 7: 実際のパフォーマンスと正確性を計測する
コールドスタートとウォームスタート、低速ネットワーク、オフラインモード、制御されていない初回アクセス、ワーカー更新を比較します。ナビゲーションからレスポンスまでの時間、TTFB、ワーカーの起動時間、プリロードヒット率、キャッシュヒット率、重複リクエスト、フォールバックエラーを記録します。平均読み込み時間だけでなく、同一性の分離とブラウザの挙動を検証します。
質の高い模範解答
私なら activate で機能検知を行い、Navigation Preload を有効化します。その後、ナビゲーションの fetch ハンドラーで最初に preloadResponse を await します。それが存在しないか不適切な場合にのみ、ハンドラーはキャッシュまたは通常の fetch を使用します。キャッシュポリシーは静的シェルとパーソナライズされた SSR HTML を分離する必要があり、サーバーはキャッシュの分離を変更することなく preload ヘッダーを利用できます。非対応ブラウザは通常の fetch の挙動を維持します。TTFB と起動時間を比較しつつ、コールドスタート、低速ネットワーク、オフラインモード、制御されていないアクセス、更新、重複リクエスト数をテストします。
よくある間違い
- 間違い: Navigation Preload を静的リソースのプリキャッシュとして扱う。 → なぜ失敗するか: ナビゲーションを対象としており、ワーカーの起動と並行して実行されるため。 → 修正方法: fetch ハンドラーで
preloadResponseを消費する。 - 間違い: プリロードから即座に結果が得られない場合に、無条件で fetch を開始する。 → なぜ失敗するか: リクエストや副作用が重複する可能性があるため。 → 修正方法: Promise、キャッシュ、fetch の実行順序を定義する。
- 間違い: パーソナライズされた HTML を共有の Cache Storage に書き込む。 → なぜ失敗するか: Cookie、Authorization、または Vary の境界が無視されるため。 → 修正方法: アイデンティティを考慮した Network-First ポリシーを使用する。
- 間違い: 平均読み込み時間のみを比較する。 → なぜ失敗するか: コールドスタート、制御されていないアクセス、重複リクエストが平均値の中に埋もれてしまうため。 → 修正方法: コホート別に TTFB、起動時間、ヒット率、エラーを追跡する。
フォローアップの質問と回答
フォローアップ 1: なぜ link preload を直接使わないのですか?
link rel=preload はページによって宣言され、リソース指向です。Navigation Preload は Service Worker の起動中に実行されるナビゲーション用のブラウザリクエストであり、ライフサイクルと消費 API が異なります。
フォローアップ 2: なぜ preloadResponse が undefined になることがあるのですか?
ブラウザがサポートしていない、リクエストがナビゲーションでない、preload が無効化されている、あるいはワーカーイベントの前にネットワークリクエストが失敗した可能性があるためです。undefined は通常の分岐として処理してください。
フォローアップ 3: Response body の二重読み取りを回避するにはどうすればよいですか?
Response body は通常1回しか消費できません。ページとキャッシュの両方でそれが必要な場合は clone() を呼び出し、両方のコンシューマーでエラーとキャンセルを適切に処理します。
フォローアップ 4: なぜ初回アクセスで高速化が見られない場合があるのですか?
制御されていないページは現在のワーカー経由でナビゲーションをルーティングせず、登録、インストール、アクティベーションに時間がかかるためです。制御されていない初回アクセスをプリロードの失敗として分類するのではなく、個別に計測してください。