service worker 缓存 html 易失败因 cache.addall() 是原子操作,任一资源 404、重定向、跨域或响应头禁止缓存即全盘失败,且 install 阶段卡住;需用 cache.put() 配 fallback 响应、按 destination 分流、版本化命名并 activate 清理旧缓存。

Service Worker 能可靠缓存 HTML,但“本地存储”不等于“永久可用”,关键在缓存策略、版本控制和 fallback 机制是否闭环。
为什么 cache.addAll() 缓存 HTML 经常失败
常见现象是 install 事件卡住、控制台报错但没提示具体哪条 URL 出问题,页面始终不被 Service Worker 控制。
-
cache.addAll()是原子操作:只要列表中任意一个请求返回非 2xx 状态(比如/index.html返回 404、302 重定向、或跨域 CORS 拒绝),整个缓存就中断,一个文件失败,全部不写入 - HTML 文件本身常带动态逻辑(如服务端渲染的 nonce、时间戳、AB 测试参数),导致每次请求响应体不同,但
cache.match()默认只比对 URL,容易命中旧缓存却渲染异常内容 - 开发时用
http://192.168.x.x或file://协议打开页面,navigator.serviceWorker.register()会静默失败,根本进不了 install 阶段
如何让 HTML 真正离线可加载且不白屏
核心不是“存进去”,而是“取出来时能兜底”。不能只依赖 caches.match(request) 返回 response,必须预设 fallback 响应。
- 在
fetch事件中,先判断request.destination === 'document',只对 HTML 请求做特殊处理,避免把 API 接口也缓存成静态页 - 缓存 HTML 时,用
cache.put()替代cache.addAll(),手动构造 fallback 响应:const fallbackHTML = new Response(`<h1>离线中</h1><p>正在尝试恢复...</p><div class="aritcle_card flexRow artxards"> <div class="artcardd flexRow"> <a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill5117" title="HTML Extract"><img src="https://img.php.cn/upload/skill/000/000/081/179033952939354.jpg" alt="HTML Extract" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a> <div class="aritcle_card_info flexColumn"> <a rel="nofollow" href="/xiazai/skill5117" title="HTML Extract" class="overflowclass">HTML Extract</a> <p class="overflowclass">使用 MinerU 从 HTML 页面和文件中提取内容,将 HTML 转换为保持标题、列表、表格及文本层次结构的干净、结构化 Markdown。F...</p> </div> <a rel="nofollow" href="/xiazai/skill5117" title="HTML Extract" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div>`, { headers: { 'Content-Type': 'text/html' } }); cache.put(new Request('/'), fallbackHTML); - 如果主 HTML 缓存失效,直接返回这个 fallback,而不是让
fetch(event.request)抛错后页面空白
缓存版本更新时旧 HTML 还在用?这是 activate 阶段没清理干净
Service Worker 更新后,老缓存不会自动删除,用户可能一直看到上一版 HTML,哪怕你已发布新版 sw.js 和新资源。
- 必须在
activate事件里显式清理旧缓存:self.addEventListener('activate', event => { const validCaches = ['myapp-v2']; event.waitUntil( caches.keys().then(keys => Promise.all( keys.map(key => !validCaches.includes(key) && caches.delete(key)) ) ) ); }); - 注意:刚注册的新 Service Worker 不会立即接管已有页面,需调用
self.skipWaiting()并配合clients.claim()才能让当前页立刻生效 - iOS Safari 不支持自动
skipWaiting(),得靠用户刷新页面或关闭标签页再打开,这点极易被忽略
CDN 容灾 + SW 本地缓存,怎么避免双重失效
当 CDN 全挂、且本地缓存又过期或损坏时,用户会彻底白屏——这正是多级容灾断点所在。
- 不要把所有希望押在单层缓存。推荐三级兜底:① SW 缓存最新 HTML(带 fallback)→ ② SW 回源到备用 CDN(如
https://backup-cdn.example.com/index.html)→ ③ 最终 fallback 到内联 HTML 字符串(硬编码在 sw.js 里,极简结构,无外部依赖) - 回源时用
fetch(url, { cache: 'no-store' }),防止中间代理缓存错误响应 - CDN 资源 URL 必须带哈希后缀(如
/index.a1b2c3.html),否则浏览器可能复用旧缓存,导致 SW 加载了错误版本
真正难的不是写几行 caches.open(),而是让每次 HTML 请求都有明确的响应路径、每个缓存生命周期都有清理出口、每种网络异常都有 fallback 形态——这些细节不串联起来,所谓“高可靠性”只是假象。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










