浏览器缓存策略:HTTP 缓存、Service Worker 与前端资源版本
本文目录
缓存不是“越久越好”,而是让不可变资源快速复用,让会变化的内容得到及时验证,并阻止个性化数据泄露到共享缓存。浏览器 HTTP cache、CDN、Service Worker Cache API 和应用内存缓存是不同层,命中任一层都可能造成“我已经发布,用户仍看旧页面”。
先给资源分类
| 资源 | 推荐策略 | 需要验证的边界 |
|---|---|---|
| 带内容 hash 的 JS/CSS/字体 | 长 max-age,并在内容变化时换 URL | 部署时保留旧 hash 文件,避免旧 HTML 引用返回 404 |
| HTML/SSR 文档 | 通常允许存储但重新验证,如 no-cache;按个性化内容补 private | CDN 规则不能覆盖掉身份差异和源站验证 |
| 公开、稳定 API | 根据更新频率选择短 max-age 或 ETag/Last-Modified | 校验 Vary 与 CDN 缓存键包含实际影响响应的维度 |
| 登录态、权限、阅读进度 | 默认不进入共享缓存;根据数据敏感级别使用 private/no-store | 切账号、登出、权限撤销时清除内存与应用缓存 |
| 用户上传与封面 | 依据访问权限、内容版本和替换方式设缓存 | 私有原书文件不能因封面可公开而继承公开缓存策略 |
no-cache 意味着复用前要验证,不是禁止存储;no-store 才是不存储新响应,但它不会自动删除之前已经缓存的对象。个性化响应还需阻止共享缓存误用。具体指令要在浏览器与实际 CDN 行为上共同验证。
HTTP 协商与部署一致性
ETag/If-None-Match 和 Last-Modified/If-Modified-Since 可减少重复传输。确保代理不会删除验证头或错误改写 Cache-Control;使用 Vary 前确认每个变化维度都进入缓存键,且不会因任意 Header 造成无限碎片。
内容 hash 静态资源采用“先上传新文件,再发布引用新文件的 HTML”的顺序。回滚时旧资源仍需存在一段兼容窗口;只回滚 HTML 而清理旧 JS,仍会导致用户加载失败。错误地长期缓存入口 HTML 会让用户一直无法发现新版本。
Service Worker 生命周期
Service Worker 可做离线、预缓存和请求路由,但也引入独立的安装、等待、激活、控制页面和缓存版本。更新时验证旧页面是否仍运行旧 bundle;skipWaiting/clientsClaim 会改变接管时机,必须设计新旧代码兼容和告知,不可只为“刷新生效”无条件抢占。
每个缓存用明确版本与资源清单,激活时只清理由该应用拥有的旧缓存,不遍历删除其他模块或同域应用数据。缓存失败时可回退网络;写缓存前校验响应状态、类型和体积。离线私有内容必须有账号边界、加密/清除策略和退出登录处理。
排查顺序
- 对照页面 HTML、响应头、ETag、Age、CDN 命中头和资源 URL。
- 确认请求实际经过浏览器 cache、Service Worker、代理还是源站。
- 查看当前页面 controller、waiting/active worker 和 cache key。
- 核验 HTML 引用的资源文件在当前和旧版本目录都可用。
- 用干净 profile、已登录/登出、切账号、硬刷新和前进返回各跑一遍。
不要把 Ctrl+F5 当成修复方法;它会掩盖发布顺序、版本键或共享缓存配置错误。缓存调试日志避免输出 cookie、授权头、书籍地址与个人响应内容。
参考:MDN HTTP caching、RFC 9111 HTTP Caching、MDN Service Worker 使用指南。
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。