一次搞懂 HTTP 缓存:强缓存、协商缓存与一个真实的踩坑记录
你有没有遇到过这种情况:前端刚发了新版本,同事却说页面还是老样子,刷新几次才正常?多半是 HTTP 缓存“帮了倒忙”。这篇短文把强缓存和协商缓存一次讲清,再分享一个真实的踩坑经历。
一、为什么需要 HTTP 缓存
浏览器每次请求都先问服务器拿资源,慢且费流量。HTTP 缓存让浏览器在一定条件下直接复用本地副本,减少请求次数、加快首屏、减轻服务器压力。代价是“新鲜度”需要管理,否则就会看到旧内容。核心思路只有一句话:能复用就复用,过期了再去问服务器有没有新版。
二、强缓存:不发请求,直接用
强缓存由响应头 Cache-Control 和 Expires 控制。浏览器收到资源后先看本地副本是否过期,没过期就直接用,根本不发网络请求(控制台 Network 里显示为 disk cache 或 memory cache)。
- Cache-Control: max-age=3600:资源在 3600 秒内有效,单位是秒,从响应生成时算起。
- Cache-Control: no-cache:不是“不缓存”,而是每次使用前必须先向服务器确认有没有新版。
- Cache-Control: no-store:不缓存,每次都重新下载,登录页、支付结果这类敏感内容用它。
- Cache-Control: immutable:资源内容永远不变,刷新页面时浏览器也不再重新确认,适合带内容哈希的静态文件。
- Expires 是老式的绝对时间写法,受本地时钟影响,一般只用 Cache-Control 即可;两者同时存在时 Cache-Control 优先。
注意一个容易混淆的点:no-cache 不等于 no-store,前者是“缓存但每次都要验证”,后者是“干脆不缓存”。
三、协商缓存:过期了,先问一句有没有新版
强缓存过期后,浏览器会带上验证信息向服务器“协商”:
- ETag / If-None-Match:服务器给资源生成一个指纹(ETag),下次请求带上 If-None-Match;指纹没变返回 304 Not Modified,浏览器继续用本地副本;变了返回 200 和新内容。
- Last-Modified / If-Modified-Since:按最后修改时间判断,精度只有秒,文件一秒内改动多次可能发现不了,ETag 更可靠;两者可以同时用,ETag 优先。
协商缓存的意义是:哪怕重新发了请求,内容没变也只返回一个空的 304,省掉了正文传输。
一次典型请求流程是:先看强缓存,命中直接用;没命中带上验证信息请求服务器,命中协商缓存返回 304,不命中返回 200 新内容。
四、真实的踩坑记录
我们有个管理后台曾经这样配:静态资源 Cache-Control: max-age=31536000,一年有效,但构建产物文件名不带哈希,app.js 永远叫 app.js。发版后用户打开的还是旧页面,清缓存才恢复。原因是:文件名没变、强缓存还在有效期,浏览器根本不会发请求问服务器。
修复办法很简单,也是一条通用经验:构建产物文件名带上内容哈希(app.a1b2c3.js),内容一变文件名就变,旧缓存自然失效;入口 HTML 用 Cache-Control: no-cache,每次打开都问服务器拿最新的入口文件,再由入口引用新的哈希文件。这样既享受了一年长缓存的加速,又不会发版后“卡在旧版本”。
五、落地清单
- HTML 入口文件:Cache-Control: no-cache,必须验证。
- JS、CSS、图片等带哈希的静态资源:Cache-Control: max-age=31536000, immutable。
- 经常变化的接口数据:默认不缓存,或用 no-store;需要缓存时用 private 并控制时间。
- 上线前在浏览器开发者工具里用“停用缓存”验证一次,并看 Network 中缓存命中情况是否符合预期。
小结
HTTP 缓存不复杂,关键是分清三件事:强缓存决定“能不能不问服务器直接用”,协商缓存决定“过期了怎么问才省流量”,而文件名哈希决定“发了新版用户能不能拿到”。把这三点配对用好,页面又快又不会“赖着不更新”。
- 点赞
- 收藏
- 关注作者
评论(0)