Service Worker 本身不支持动态回滚,需构建层注入版本上下文、运行时基于失败信号触发降级、缓存命名隔离三者协同:HTML 注入 __SW_VERSION 或 Cookie 传参,SW 从 scriptURL 或 cookie 读取版本,缓存名用语义化版本(如 static-v2.4.0)而非时间戳,fetch 中构造带版本的 cache key,关键资源连续3次失败即降级至白名单内上一稳定版本并上报。

Service Worker 本身不支持“动态回滚”,它没有状态感知能力,也不能主动判断资源是否异常。所谓“动态回滚”,必须由构建层注入版本上下文、运行时基于失败信号触发降级逻辑、并配合缓存命名隔离来实现——否则你写的 skipWaiting() 或 self.clients.claim() 都只是换了个缓存,不是回滚。
如何让 Service Worker 知道“当前该用哪个版本”
页面必须显式告诉 SW 当前期望的版本号,不能靠 SW 自己猜。常见错误是把版本写死在 sw.js 里,导致灰度用户和稳定用户加载同一份 SW,失去隔离基础。
- 在 HTML 模板中注入全局变量:
window.__SW_VERSION = '2.4.0',或通过Cookie传递gray_version=2.4.0 - 注册 SW 时带版本参数:
navigator.serviceWorker.register('/sw.js?v=2.4.0'),防止浏览器复用旧脚本 - SW 内部读取版本的方式要统一:优先从
self.registration.active?.scriptURL解析, fallback 到document.cookie或self.clients.matchAll()中获取主页面上下文
为什么 cache 名称必须带语义版本,而不是用时间戳或随机值
缓存名称(如 static-v2.4.0)是回滚的锚点。如果用 cache-v1678901234 这类不可读标识,你就无法在 fetch 失败后精准切换到“上一个已知稳定版本”——因为你不知道哪个是 v2.3.1,哪个是 v2.3.0。
- 安装阶段只打开并填充当前版本缓存:
caches.open('static-' + VERSION),不 touch 其他缓存 - 激活阶段清理非白名单缓存:
['static-v2.4.0', 'static-v2.3.1']是保留列表,其余全删 - fetch 时构造 cache key 必须包含版本:
request.url + '?v=' + currentVersion,否则caches.match()会跨版本误命中
如何检测失败并触发本地回滚逻辑
不能等整个页面白屏才行动。关键资源(首屏 JS/CSS/HTML)需单独监控失败次数,连续 3 次失败即降级,且降级动作必须可观察、可上报。
- 在
fetch事件中对导航请求(request.destination === 'document')和关键静态资源(request.url.endsWith('.js') || request.url.endsWith('.css'))做计数 - 失败条件包括:
response.status >= 500、!response.ok、fetch() 抛错 - 降级时改用上一版本缓存名(如从
static-v2.4.0切到static-v2.3.1),并返回响应头headers.set('X-SW-Fallback', 'true')供前端识别 - 前端监听
message事件接收 SW 发来的{ type: 'fallback', from: '2.4.0', to: '2.3.1' },用于打点和 UI 提示
真正难的不是写几行 caches.match(),而是让每个环节都可追溯:构建产出的 manifest 是否匹配 SW 脚本中的版本?CDN 返回的 HTML 是否真的携带了正确的 __SW_VERSION?失败计数器有没有被跨 tab 共享污染?这些细节一旦漏掉,回滚就会变成“看似在动,实则卡死”。










