Service Worker 缓存未同步的根源在于脚本更新与缓存内容更新脱节:install 阶段未重缓存或 activate 阶段未清理旧缓存,导致页面加载时命中过期资源;需用唯一缓存名、skipWaiting、精准 activate 清理、claim、HTML 绕缓存及 controllerchange 监听确保更新生效。

Service Worker 频繁更新本身不是问题,真正引发客户端缓存未同步的,是新 SW 激活前旧缓存仍被复用、或新缓存未按预期覆盖旧资源——本质是生命周期控制与缓存版本管理脱节。
明确区分“SW 脚本更新”和“缓存内容更新”
两者不同步是根源:SW 脚本变了,但 install 阶段没重新缓存资源;或缓存了,activate 阶段又没清理旧缓存名。结果就是页面加载时 fetch 匹配到过期缓存项,返回空响应或错版文件,导致白屏、404 或样式错乱。
- 每次构建必须生成唯一缓存名(如 sw-v2.3.1-8a9b0c),不能复用固定名(如 static-cache)
- install 回调中只缓存当前版本所需资源,URL 必须与 HTML 中引用的完全一致(含 hash)
- 避免在 install 中调用 caches.delete() —— 清理动作只应在 activate 阶段做
强制接管 + 精准清理,堵住旧缓存漏网
光靠用户刷新或关页无法保证同步,需主动推进生命周期并切断旧缓存路径:
- 在新 SW 的 install 事件开头调用 self.skipWaiting(),跳过 waiting 状态
- 在 activate 事件中用 event.waitUntil() 包裹清理逻辑,遍历 caches.keys(),删除所有不匹配当前缓存命名规则的缓存(例如正则 /^sw-v(?!2\.3\.1)/)
- 紧接着调用 self.clients.claim(),确保已打开的页面立即由新 SW 控制
HTML 请求必须绕过缓存,打破死循环
index.html 是整个更新链的起点。若它被旧 SW 从过期缓存中返回,后续 JS/CSS 引用必然错配:
- fetch 事件中判断 event.request.destination === 'document',直接 return fetch(event.request),不查任何 cache
- 服务端对 /index.html 响应头设为 Cache-Control: no-cache, must-revalidate,防止 HTTP 缓存干扰 SW 更新
- 避免在 HTML 中写 ,微信等环境不认
上线后主动触发一次冷更新流
长期驻留的浏览器标签页可能卡在旧 SW 控制下,即使部署了新版也不会自动拉取:
- 页面 JS 中注册 navigator.serviceWorker.addEventListener('controllerchange', () => { location.reload(true) })
- 或更友好地提示用户“有新版本,点击刷新体验”,避免强制 reload 影响表单等状态
- 同时确保服务端 /sw.js 响应头也是 no-cache,否则连新脚本都拿不到











