HTML缓存更新问题本质是浏览器未获取新HTML:Service Worker拦截、Nginx强缓存、CDN缓存/version.json误缓存及SW缓存名复用均导致旧HTML被加载,须禁用强缓存、动态命名SW缓存、强制version.json不缓存并确保/index.html显式加入SW缓存。

HTML 缓存更新问题不是“要不要缓存”,而是“浏览器到底有没有真正拿到新 HTML”。绝大多数线上故障,根源是浏览器压根没发请求——它从磁盘 cache 里直接读了旧字节,连服务端的 /version.json 都没机会触发。
为什么强制刷新(Ctrl+F5)有时也不生效
Service Worker 拦截了所有请求,且返回的是旧缓存里的 /index.html。此时 Ctrl+F5 只是绕过 HTTP 缓存,但 SW 仍会 event.respondWith(cache.match(event.request)) 返回过期内容。用户看到的仍是旧 DOM 树,document.querySelector('.new-button') 必然为 null。
- 检查控制台 Application → Service Workers 面板,确认当前激活的 SW 脚本是否已更新、状态是否为 “Activated”
- 在 SW 的
fetch事件中,对request.destination === 'document'的请求,必须显式 fallback 到cache.match('/offline.html')或fetch(),不能无条件返回缓存 - 安装新 SW 后,若页面仍被旧 SW 控制,需调用
self.skipWaiting()+clients.claim(),否则用户得关掉所有标签页再重开
Nginx 对 /index.html 的缓存头必须禁用强缓存
如果 Nginx 返回了 Cache-Control: public, max-age=31536000,浏览器就永远不发请求——你改了十次 HTML,用户看到的都是第一次加载时存下的副本。这不是前端能修复的问题。
- 正确配置(Nginx):
location = /index.html { add_header Cache-Control "no-cache, must-revalidate"; etag on; if_modified_since exact; } - 避免用
max-age=0:它仍会触发协商缓存(304),但若后端没返回有效ETag或Last-Modified,实际效果等同于每次都下载整份 HTML - CDN(如 Cloudflare)必须单独排除
/index.html路径,禁用其“Cache Everything”规则;否则源站头会被覆盖
Service Worker 缓存名不变 = HTML 更新必然失效
复用同一个缓存名(如 caches.open('pages'))会导致新旧 HTML、JS、CSS 全部混在一个缓存实例里。cache.addAll() 不会覆盖,只会追加。结果就是:离线时返回 v1 的 HTML,却执行 v2 的 JS —— import('./utils.js') 报错,因为路径在 v1 HTML 里根本不存在。
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
立即学习“前端免费学习笔记(深入)”;
- 每次部署必须变更缓存名,推荐格式:
pages-${VITE_APP_VERSION}或assets-v2.3.1(构建时注入,别手写) -
install事件中只打开新缓存:caches.open('pages-v2.3.1'),并addAll(['/index.html', '/offline.html'])—— 注意必须用绝对路径,且/index.html必须显式列出 -
activate事件中清理旧缓存:caches.keys().then(keys => Promise.all(keys.filter(k => k !== 'pages-v2.3.1').map(k => caches.delete(k))))
/version.json 请求被缓存是最隐蔽的失败点
前端轮询 /version.json 发现版本没变,就认为无需更新。但这个请求本身如果被 CDN 或浏览器缓存了,它返回的永远是旧值 —— {"version": "a1b2c3"},而实际已是 "d4e5f6"。
- 服务端必须返回
Cache-Control: no-cache, must-revalidate,且响应体为纯 JSON(无 BOM、无注释、无空格包裹) - 前端
fetch('/version.json')必须显式设cache: 'no-store',不能依赖默认行为;cache: 'default'会被 HTTP 层拦截 - 比对逻辑必须在
DOMContentLoaded前执行,例如放在<head>内联脚本中;否则用户已渲染出旧 DOM,再 reload 已晚
最易被忽略的一点:HTML 文件本身是否进了 Service Worker 缓存桶,和它在构建产物里是否存在、路径是否正确完全无关。只看 cache.addAll() 里有没有那个绝对路径字符串 —— 少写一个 /,或者用了相对路径,离线时就是白屏,连错误都不会报。


















