离线降级页面需通过service worker实现“永久可用”:预缓存带版本号的纯静态/offline.html及依赖资源,install阶段原子写入;fetch中直接返回缓存、禁用网络验证;更新时变更缓存名并调用skipwaiting,服务器响应头设no-store交由sw统一管控。

离线降级页面(比如“网络不可用”提示页)需要真正“永久可用”,但浏览器不支持所谓“永久缓存”——所有缓存都有生命周期,关键在于**让它不被自动淘汰、不依赖网络验证、且更新可控**。配置的核心不是设 max-age=31536000,而是靠 Service Worker 主动接管 + 版本化预缓存 + 禁用协商验证。
预缓存降级页,带版本号隔离
把降级页(如 /offline.html)和它的资源(CSS、图标)在 Service Worker 的 install 阶段一次性写入缓存,并用固定版本标识,例如:
- 缓存名设为
offline-v1,后续更新时改为offline-v2 - 用
caches.open().addAll([...])原子写入,任一文件失败则整个 install 失败,避免半残缓存 - 确保该页面本身不包含动态内容或服务端渲染逻辑,纯静态 HTML
fetch 中强制走缓存,跳过网络检查
当检测到网络异常时,Service Worker 的 fetch 事件应直接返回预缓存的降级页,不尝试发起网络请求:
- 监听
navigator.onLine === false或 fetch 请求 reject 后触发降级 - 匹配请求 URL 是主页面(如
/,/article/123)时,直接caches.match('/offline.html') - 不要用
StaleWhileRevalidate或NetworkFirst,这类策略仍会发网络请求,违背“离线即刻响应”目标
手动刷新机制:靠版本号+skipWaiting 触发更新
“永久缓存”不等于“永不更新”。要支持人工发布新版降级页,需做到两点:
- 每次修改
offline.html,同步更新 SW 脚本里的缓存版本号(如从v1→v2),并调用self.skipWaiting() - 在页面中监听
controllerchange事件,提示用户“有新版本,点击刷新体验优化”,或自动执行window.location.reload(true) - 避免用
location.reload()不带参数,它可能命中浏览器内存缓存,加true强制绕过缓存重拉
别碰 HTML 的 HTTP 缓存头
服务器给 offline.html 返回的响应头里,不要设置强缓存(如 Cache-Control: immutable, max-age=31536000):
- 浏览器可能在 SW 尚未激活时就缓存了旧 HTML,导致新 SW 注册后仍加载旧版
- 正确做法是让服务器返回
Cache-Control: no-store或短时效(max-age=0),把控制权完全交给 SW - SW 才是唯一可信的缓存管理者,HTTP 缓存在这里只是干扰项











