service worker注册失败的三个硬性条件是:页面未走https(localhost除外)、sw.js返回非2xx状态、注册路径错误;三者任一不满足都会导致navigator.serviceworker.register()静默失败。

Service Worker注册失败的三个硬性条件
页面没走 HTTPS(localhost 除外)、sw.js 返回非 2xx 状态、注册路径写错——这三件事只要有一件不满足,navigator.serviceWorker.register() 就会静默失败,连错误都不抛。你看到“离线白屏”,大概率卡在这一步。
常见踩坑点:
-
http://127.0.0.1不行,必须是http://localhost或https://开头 -
sw.js路径写成'./sw.js'或'sw.js':它相对于当前页面 URL 解析,不是 JS 文件位置;页面在/blog/post.html,要注册根目录下 SW,就得用'/sw.js' - 服务器给
sw.js加了Cache-Control: no-store:浏览器直接拒绝注册,Nginx/Apache 默认配置有时会这么干
验证方式:打开 DevTools → Application → Service Workers,状态必须是 “Active”;如果一直是 “Waiting” 或 “Redundant”,说明新版本没接管,旧 SW 还占着位。
install 阶段缓存 HTML 依赖链必须完整
cache.addAll() 是原子操作:列表里任意一个资源 fetch 失败(404、CORS、MIME 错误、响应头禁止缓存),整个缓存就中止,一个文件都不会写入。你写的 ['/index.html'] 看似没问题,但页面加载时还会发请求去拿 /main.css、/app.js、/fonts/icon.woff2——漏掉任何一个,离线时就 net::ERR_FAILED。
实操建议:
- 所有路径必须是绝对根路径:
'/index.html'✅,'index.html'❌,'/static/app.js?v=1.2'❌(查询参数导致缓存键无法复用) - 提前校验关键 URL:在
install里加console.log('caching:', urlsToCache),再对每个 URL 手动fetch(url).then(r => r.ok ? r : Promise.reject(r)) - 动态生成的 HTML(如 SSR 页面)若带
Cache-Control: no-store,cache.addAll()直接拒收——要么改服务端响应头,要么改用cache.put()+new Response(htmlString)构造 fallback
fetch 事件里必须按 request.destination 分流处理
只写一行 event.respondWith(caches.match(event.request) || fetch(event.request)) 是最危险的写法。它会让 POST 表单、带 credentials: 'include' 的登录请求也去查缓存,结果就是提交无声无息、用户以为成功了其实什么都没发出去。
正确做法是按类型分流:
-
event.request.destination === 'document':这是页面首次打开或刷新的关键请求,必须命中缓存,否则白屏;建议 fallback 到预存的'/offline.html'字符串响应 -
=== 'script'或=== 'style':可直接caches.match(),静态资源缓存优先没问题 -
=== 'font'或=== 'image':建议包一层try/catch,失败就跳过缓存,避免单个图标缺失阻断整页渲染 -
=== 'json'或空字符串(如fetch('/api/user')):这类 API 请求应放行到网络,或走stale-while-revalidate,别塞进主离线缓存
缓存命名和清理必须显式控制
缓存名写死成 'v1' 看似简单,但更新资源后,新 SW 安装时旧缓存不会自动删,浏览器可能继续用过期的 app.js,而你根本不知道。更糟的是,旧缓存永远占位,越积越多。
实操要点:
- 缓存名建议带构建时注入的哈希或时间戳:
static-v${VERSION},其中VERSION来自 Webpack 的DefinePlugin或 Vite 的define -
activate阶段必须调用caches.delete()清理非当前版本的缓存,否则旧缓存永不释放 - 不要依赖
self.skipWaiting()强制激活——它会让新 SW 立即接管已有页面,可能造成 JS 版本错配;更稳妥的是监听controllerchange后手动刷新
真正难的不是写几行代码,而是让缓存策略和你的构建流程、路由模式、HTTP 响应头完全对齐。比如 Vue Router history 模式下,所有路由都返回同一份 /index.html,但如果你只缓存了 '/',而没处理 /user/profile 的 fallback,离线访问该路径就会 404。这种细节不靠调试,靠逐条比对请求链和缓存键。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











