題目與背景
你的網站用 Service Worker 攔截導覽請求,以支援離線回退與統一快取。使用者發現首次導覽必須等待 Service Worker 啟動後才開始網路請求。請設計 navigation preload,要求相容不支援該 API 的瀏覽器,並避免重複請求、舊快取與回應競態。
面試官考察什麼
回答應知道 navigation preload 是瀏覽器在 Service Worker 啟動期間並行發起的導覽請求,不是預快取,也不是任意資源的 link rel=preload。必須涵蓋 activate 啟用、fetch 事件讀取 preloadResponse、網路與快取優先級、Service-Worker-Navigation-Preload 標頭、逾時與失敗回退,以及指標驗證。
先問清楚的澄清問題
頁面與快取目標
確認導覽是 SSR HTML、SPA shell 或離線頁,哪些路徑可快取,以及使用者資料是否必須避開共享快取。個人化回應不可無條件寫入公共快取。
生命週期與瀏覽器支援
確認 Service Worker 的註冊、更新與控制範圍,以及目標瀏覽器是否支援 navigationPreload。尚未受控制的第一次導覽不能假定會經過目前 Worker。
網路與一致性策略
明確網路優先、快取優先或 stale-while-revalidate,並定義網路失敗時的離線回應。伺服器也要決定如何讀取自訂 preload 標頭,避免它改變快取鍵。
30 秒回答框架
「在 Service Worker 的 activate 事件中檢查並啟用 navigation preload。瀏覽器會在 Worker 啟動時並行請求導覽;fetch 事件先讀取 event.preloadResponse,沒有結果才依策略查快取或發起普通 fetch。只接受符合 URL、身分與快取策略的回應,避免 preload 與普通 fetch 重複。用支援率、啟動耗時、TTFB、命中與錯誤回退比較效果。」
深入解答步驟
第一步:在 activate 啟用
等待 registration.navigationPreload 存在後呼叫 enable(),通常放在 activate 的 waitUntil,確保新 Worker 啟用前完成設定。可用 setHeaderValue() 設定識別標頭,但不要把隱私或不可快取狀態放進標頭值。
第二步:在 fetch 事件消費 preloadResponse
只對導覽請求讀取 event.preloadResponse。它可能解析為 Response,也可能是 undefined。先使用合法 preload 回應;沒有回應時才查快取或發起 fetch,避免同時啟動第二個網路請求。
第三步:定義快取與身分邊界
SSR HTML 若含身分、地區或實驗分組,應依既有快取鍵與回應標頭處理,不能把 preload 回應直接寫入 Cache Storage。靜態 shell 可快取優先,個人化頁面則可網路優先。
第四步:處理標頭與伺服器路由
伺服器可讀取 Service-Worker-Navigation-Preload 標頭識別並行請求,決定是否略過昂貴工作或採用專用快取。代理和 CDN 要明確是否轉發、忽略或納入快取鍵。
第五步:處理競態、逾時與錯誤
preload 失敗、逾時或狀態不適合時,依策略轉向快取或普通 fetch。Response body 不可重複讀取;需要兩個分支時使用 clone(),並控制逾時與取消。網路錯誤後才嘗試離線頁,不要覆蓋真實伺服器錯誤。
第六步:漸進降級與更新
不支援 navigation preload 時繼續使用普通 Service Worker fetch;不支援 Service Worker 時直接走網路。更新 Worker 時保留舊版本快取協定,待新版本可用後再清理。
第七步:驗證真實效能與正確性
對照冷啟動、熱啟動、慢網、離線、未控制首訪與更新中的 Worker,記錄導覽到回應、TTFB、Worker 啟動、preload 命中、快取命中、重複請求與錯誤回退。檢查身分、快取隔離與跨瀏覽器支援,不只看平均時間。
高品質示例回答
我會在 activate 中 feature-detect 並啟用 navigation preload,在導覽 fetch 事件先等待 preloadResponse,只有沒有可用回應時才走快取或普通 fetch。快取策略按頁面是否個人化拆分,避免把帶 Cookie 的 SSR HTML 寫入共享快取;伺服器可用標頭識別請求,但不能破壞快取鍵。測試覆蓋冷啟動、慢網、離線、未控制首訪、更新和重複請求。
常見錯誤
- 錯誤: 把 navigation preload 當成靜態資源預快取。→ 原因: 它只針對導覽並與 Worker 啟動並行。→ 改進: 在 fetch 事件消費
preloadResponse。 - 錯誤: preload 沒結果時無條件再發網路請求。→ 原因: 會造成重複請求和副作用。→ 改進: 明確 Promise、快取與 fetch 順序。
- 錯誤: 將個人化 HTML 寫入共享 Cache Storage。→ 原因: 忽略 Cookie、Authorization 或 Vary。→ 改進: 按身分和快取鍵設計網路優先。
- 錯誤: 只比較平均載入時間。→ 原因: 冷啟動、首訪和重複請求問題被掩蓋。→ 改進: 分層記錄 TTFB、啟動、命中與錯誤指標。
追問與回答
追問 1:為什麼不直接使用 link preload?
link rel=preload 由頁面宣告並針對資源;navigation preload 是瀏覽器為導覽在 Service Worker 啟動期間發起的並行請求,目的與生命週期不同。
追問 2:preloadResponse 為什麼可能是 undefined?
瀏覽器可能不支援、請求不是導覽、preload 被停用,或網路請求在 Worker 事件前失敗。程式必須把 undefined 視為正常分支。
追問 3:如何避免 Response body 被讀兩次?
Response body 通常只能讀一次。若要把同一回應交給頁面並寫入快取,先使用 clone(),並處理兩個讀取分支的例外與取消。
追問 4:首次訪問為何可能沒有加速?
頁面尚未受 Service Worker 控制時,導覽不會經過目前 Worker;註冊、安裝和啟用也需時間。應單獨統計未控制首訪。