service worker 无法更新的主因是 http 强缓存拦截了 sw.js 请求,导致浏览器复用旧缓存而非下载新版本;必须禁用强缓存,配置 cache-control: no-cache, must-revalidate,并配合 skipwaiting 和 clients.claim 实现立即接管。

Service Worker 无法更新,最常见的“隐形凶手”就是 HTTP 强缓存。浏览器在注册或检查 SW 脚本(如 sw.js)时,如果从本地磁盘缓存或 CDN 直接返回了旧版本字节,就不会触发更新流程——哪怕你已重新部署、重写逻辑、甚至改了文件内容,浏览器也“看不见”变化。
为什么强缓存会拦住 Service Worker 更新?
Service Worker 的更新机制依赖一个关键判断:两次 fetch 到的 SW 脚本内容是否**字节级不同**。如果服务器返回了 Cache-Control: max-age=31536000 或 ETag 未变,浏览器就跳过网络请求,直接复用缓存副本。结果是:
- 新 SW 文件根本没被下载,install 阶段不触发
- reg.waiting 始终为 null,页面收不到更新提示
- 调用
registration.update()也无响应
必须禁用 SW 脚本的强缓存
不是“建议”,而是硬性要求。需确保每次请求 sw.js(及类似入口脚本)都走真实网络。推荐配置如下:
-
开发与预发环境:末尾加时间戳注释,例如
// build:202605231945,让每次构建后脚本内容字节不同 -
生产环境:服务器返回严格缓存头:
Cache-Control: no-cache, must-revalidate, no-store
或至少:Cache-Control: no-cache, max-age=0 -
若用 Webpack/Vite 构建:通过插件注入响应头(如
vite-plugin-static-copy配合自定义 header),或由 Nginx/Apache 显式设置路径规则,例如:location = /sw.js { add_header Cache-Control "no-cache, must-revalidate"; }
配合 skipWaiting + clients.claim 确保立即接管
即使 SW 成功更新,若不主动推进生命周期,它可能卡在 waiting 状态。在新 SW 脚本中加入:
self.addEventListener('install', (e) => {
e.waitUntil(self.skipWaiting());
});
self.addEventListener('activate', (e) => {
e.waitUntil(
Promise.all([
self.clients.claim(),
caches.keys().then(keys =>
Promise.all(keys.map(k => k.startsWith('my-cache-') && caches.delete(k)))
)
])
);
});
注意:skipWaiting() 应只在确认新版本可安全上线时使用(如无破坏性变更),避免用户中途刷新导致 JS/CSS 版本错配。
验证是否真正生效
别只看控制台日志。实操验证三步:
- 打开 Chrome DevTools → Network 标签,过滤
sw.js,检查 Response Headers 中的Cache-Control是否符合预期 - 刷新页面后,在 Application → Service Workers 面板查看当前状态:是否出现 “waiting” 或 “activating”,controller 是否已切换
- 手动点击 “Update on reload”,观察是否触发 install → activate 全流程;若失败,说明仍被缓存拦截










