HTTP 缓存在前端项目中如何设计?
简化版
前端缓存设计通常是 HTML 短缓存或不强缓存,带 hash 的 JS/CSS/图片长期强缓存,接口数据按业务设置缓存策略。强缓存减少请求,协商缓存减少传输,配合 CDN 和版本化文件名能兼顾性能和更新。
详细版
常见策略:
- HTML:
no-cache或短缓存,确保能拿到最新资源引用。 - JS/CSS:文件名带内容 hash,设置长期强缓存。
- 图片字体:带 hash 可长期缓存,不带 hash 要谨慎。
- API:根据业务设置
Cache-Control、ETag 或前端缓存。
如果 HTML 被长期缓存,用户可能一直引用旧 JS。
如果静态资源没有 hash 却长期缓存,发布后也可能不更新。
合理缓存要同时考虑性能、版本更新、回滚和 CDN 刷新。
完整版教学
一、缓存设计的核心矛盾
缓存越强,访问越快;缓存越强,更新越难。前端缓存设计就是在速度和更新之间找平衡。
带内容 hash 的资源天然适合长缓存,因为内容变了文件名也变。HTML 通常不适合长缓存,因为它是资源入口。
二、强缓存和协商缓存
强缓存命中时浏览器不请求服务器,速度最快。常由 Cache-Control: max-age=... 控制。
协商缓存会向服务器验证资源是否变化。如果没变,服务器返回 304,浏览器继续用本地缓存。
强缓存省请求,协商缓存省响应体。
三、发布顺序
前端发布常见顺序是先上传带 hash 的静态资源,再发布 HTML。这样可以避免 HTML 已经引用新资源,但 CDN 上新资源还不存在。
回滚时也要保证旧版本资源没有被删除,否则 HTML 切回旧版本后会引用不到旧文件。
四、面试追问与工程落地
面试官可能问:“为什么 index.html 通常不长期强缓存?”
因为它决定加载哪个版本的 JS/CSS。如果 HTML 缓存太久,用户可能一直停留在旧入口,无法拿到新版本。
工程中要结合 CDN 缓存、资源 hash、版本号和灰度发布一起设计缓存策略。
五、指令语义和缓存层次
no-cache 不是“不缓存”,而是复用前必须向源站验证;真正禁止存储使用 no-store。共享缓存和浏览器私有缓存还要区分:
| 指令 | 语义 | 常见场景 |
|---|---|---|
max-age=31536000, immutable | 一年内直接复用 | 内容 hash 静态资源 |
no-cache | 可存储,每次复用前验证 | HTML 入口 |
no-store | 不得存储 | 高敏感响应 |
private | 只允许私有缓存 | 用户个性化数据 |
s-maxage=60 | 共享缓存使用 60 秒 | CDN 公共 API/HTML |
若 1MB JS 被访问 10000 次,长强缓存理论上可省近 10GB 响应体传输;但前提是 URL 内容不可变。文件名不变却缓存一年,节省的流量会换成无法及时修复线上代码的代价。
缓存设计不是给所有资源一个 TTL,而是同时设计“资源身份、谁能缓存、多久复用、如何验证、怎样失效”。
六、协商、Vary 和发布闭环
ETag 变化时返回新 200,不变时返回 304;Last-Modified 精度和语义较弱,通常作为另一种验证器。304 没有响应体,但请求往返仍存在,所以它不等于强缓存的零网络成本。
Cache-Control: public, max-age=31536000, immutable
ETag: "content-version"
Vary: Accept-Encoding
Vary 决定缓存 key 还受哪些请求头影响。缺少必要 Vary 可能把压缩格式、语言或 Origin 响应串用;Vary 维度过多又会碎片化缓存、降低命中率。
发布必须先保证新旧 hash 资源都可用,再更新 HTML。排查旧版本时查看 DevTools 的 from memory/disk cache、Age、ETag、CDN 命中头和最终 HTML 引用,分别判断浏览器、代理、CDN 或源站哪一层陈旧。
对于 Service Worker 缓存,还要单独设计版本和更新生命周期;它可以绕过普通 HTTP 缓存路径返回旧响应。排查时应确认请求究竟来自 HTTP cache、Cache Storage 还是网络。
若响应带用户身份或权限差异,还必须先确认共享缓存不会跨用户复用,再讨论命中率优化。
七、常见误区与追问
- 误区:
no-cache表示浏览器完全不能保存。 它允许存储,但每次复用前必须验证。 - 误区:304 和强缓存命中成本相同。 304 仍需一次网络往返,强缓存可直接本地复用。
- 误区:资源带 hash 后可以立刻删除旧版本。 旧 HTML、回滚版本和已打开客户端仍可能请求旧 URL。
- 追问:为什么 HTML 通常不设一年强缓存? 它是资源版本入口,长期陈旧会让用户一直引用旧 bundle。
- 追问:
private与no-store有何区别? private 允许浏览器私有缓存,no-store 禁止任何缓存存储。 - 追问:ETag 与 Last-Modified 如何选? ETag 更能表达内容版本,Last-Modified 简单但受时间精度和生成逻辑限制。
- 追问:Vary 为什么可能降低命中率? 每个请求头取值组合都可能形成独立缓存对象。
八、加强记忆
- 资源身份:内容 hash 把“内容变化”转成“URL 变化”。
- 入口策略:HTML 可更新,hash 资源长期 immutable。
- 指令区分:no-cache 要验证,no-store 才是不存。
- 验证成本:304 省响应体但不省 RTT,强缓存两者都省。
- 共享边界:public/private/s-maxage 与 Vary 决定 CDN 是否安全复用。
- 发布保障:先资源后 HTML、保留旧资源、支持灰度和回滚。