如何用Service Worker实现即使在无网环境下也能显示的离线提示页

作者:袖梨 2026-07-21
Service Worker离线提示页必须预缓存静态/offline.html路径,仅响应导航请求(destination==='document'或mode==='navigate'),用503状态码构造Response返回,且该HTML须零外部依赖。

能实现,但必须提前缓存提示页本身,且不能依赖运行时 fetch 拦截失败后跳转——Service Worker 的离线响应必须来自 cache storage 或 self.skipWaiting() 之后的已激活状态。

注册时确保 Service Worker 已安装并激活

很多开发者以为注册完就能立刻拦截请求,其实注册只是触发 install 阶段,而 fetch 事件只在 activate 后生效。如果用户首次访问就断网,install 阶段失败(比如提示页资源没加载完),整个离线逻辑就崩了。

  • navigator.serviceWorker.register() 后要监听 waitingactive 状态,必要时调用 sw.waiting.postMessage('skipWaiting')
  • install 事件里用 event.waitUntil(caches.open('offline-v1').then(cache => cache.addAll(['/offline.html']))) 预缓存提示页,路径必须和后续 fetch 中匹配
  • 不要在 install 里缓存动态路由(如 /user/123),/offline.html 必须是静态可预知路径

fetch 事件中区分导航请求与资源请求

浏览器对页面导航(request.destination === 'document')和脚本/CSS/图片等资源的处理逻辑不同。离线提示页只应响应导航请求,否则可能把 JS 文件也替换成 HTML,导致白屏或报错。

  • if (request.destination === 'document') 过滤,避免误劫持 .js.css 请求
  • 检查 event.request.mode === 'navigate' 更稳妥,因为部分浏览器对直接地址栏输入也会发 destination: 'document'
  • 不要用 fetch(request).catch(...) 做兜底——离线时 fetch 会直接 reject,但你得在 reject 前就决定返回缓存还是 fallback

返回离线页时注意响应头与状态码

直接 Response.redirect('/offline.html') 在离线时会失败(重定向需要网络),必须用 new Response(body, { status: 503, headers: { 'Content-Type': 'text/html' } }) 构造完整响应。

  • 从 cache 中读取 /offline.html 后,要用 response.text() 解析 body,再 new Response(body, options),不能直接 return response —— 因为 cache.put 保存的是 Response 对象,但 cache.match 返回的 response.body 已被读取过一次,需重新构造
  • 状态码建议用 503(Service Unavailable)而非 200,便于调试时在 DevTools Network 面板一眼识别是离线 fallback
  • 确保 /offline.html 自身不引用任何外部资源(如 CDN 字体、第三方 JS),否则它自己也会加载失败

最常被忽略的一点:缓存策略不是“有就行”,而是“install 时必须成功写入 + activate 后能稳定读取”。哪怕只差一个斜杠(比如缓存了 offline.html 却在 fetch 里匹配 /offline.html),或者 HTML 里写了 <script src="/js/app.js"></script> 但没缓存该 JS,离线页都会半残。真实环境里,多一个相对路径、少一个 event.waitUntil 包裹,就足以让整个离线流程静默失效。

相关文章

精彩推荐