service worker html缓存失效主因是请求未进入fetch事件:首次访问时sw未激活,html已发出;后续可能被navigation preload绕过,且install阶段常未正确预缓存html路径或未校验响应状态。

HTML 的 Service Worker 运行时缓存,核心不是“等用户访问再缓存”,而是“拦截请求 + 匹配已有缓存 + 按需更新”。直接在 fetch 事件里无条件 caches.match(event.request),对 HTML 文件大概率失效——因为首次访问时 SW 还没激活,HTML 请求早已发出;后续又可能被 navigation preload 绕过。
为什么 HTML 缓存总在离线时白屏或 404
常见错误现象:DevTools → Application → Cache Storage 里能看到 CSS/JS 被缓存了,但 / 或 /index.html 不在缓存中;离线刷新页面直接报 “Failed to load resource: net::ERR_INTERNET_DISCONNECTED”。
- 根本原因不是缓存逻辑写错了,而是 HTML 请求没进到你的
fetch事件里——它被 navigation preload 自动发走了,而你没禁用或处理它 -
cache.match()对 HTML 请求返回undefined,往往是因为 install 阶段根本没预缓存 HTML 路径(比如只写了'/index.html',但实际服务端重定向到了'/') - 后端返回 304 Not Modified 时,
response.body是空流,cache.put(request, response)会静默失败,必须先response.clone().status === 200做校验
fetch 事件里怎么安全拦截 HTML 请求
关键不是“所有请求都走缓存”,而是识别出 document 类型请求,再做分层响应。不能只靠 caches.match(event.request) || fetch(event.request)。
- 必须加判断:
if (event.request.destination === 'document'),否则图片、API 等请求也会被误判 - 优先 match 缓存,但 fallback 到 network 时要确保能触发重新缓存(尤其对带哈希的构建产物):
event.respondWith( caches.match(event.request).then(cached => { if (cached) return cached; return fetch(event.request) .then(response => { if (response.status === 200) { const cloned = response.clone(); event.waitUntil( caches.open('html-cache-v1').then(cache => cache.put(event.request, cloned)) ); } return response; }); }) ); - 禁用 navigation preload(除非你明确 handle 它):
self.addEventListener('activate', event => { event.waitUntil(self.clients.claim()); }); // 并在 register 时传配置 navigator.serviceWorker.register('/sw.js', { type: 'module' }).then(r => { r.navigationPreload.enable(); // ← 别开!或者开完立刻在 fetch 里处理 preloadedResponse });
install 阶段预缓存 HTML 的坑点
很多人以为 cache.addAll(['/', '/index.html']) 就够了,但实际部署中常因路径语义不一致导致缓存空转。
- 如果用 Vue Router / React Router 的 history 模式,服务端 Nginx 配置是
try_files $uri $uri/ /index.html,那你要预缓存的必须是'/',而不是'/index.html'——否则离线时 SW 找不到匹配项 - Webpack/Vite 构建后,真实 HTML 可能带 hash,如
/index.a1b2c3.html,但 service worker 脚本里写的还是字面量路径,缓存就失效了;得配合构建插件生成 manifest,或用workbox-webpack-plugin自动注入 -
cache.addAll()失败不会抛错,但整个 install 会失败变成Redundant;建议用cache.add()单个 try-catch 并 log 错误 URL
HTML 缓存更新时最易忽略的一环
HTML 文件本身变化频率低,但它的内容依赖 JS/CSS bundle。如果 SW 缓存了旧版 index.html,却加载了新版 app.8f9e0d.js,React/Vue hydration 会直接崩溃,控制台报 Hydration failed because the initial UI does not match what was rendered on the server。
这不是缓存策略问题,而是版本对齐问题:HTML 和它引用的资源必须属于同一发布批次。要么让 HTML 文件名也带 hash(如 Vite 默认的 index.html 不带 hash,需手动配置),要么在 activate 阶段强制清空 HTML 缓存并 reload 页面——但 reload 本身又依赖网络。真正可行的解法,是在每次构建后生成一个唯一 CACHE_VERSION,并在 install 和 fetch 中严格绑定该版本号,避免跨版本混用。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











