博客 / 教程
教程 · 阅读约 3 分钟 · 15 次阅读 · 发布于 2026-09-27 · FORMA 团队
HTTP 缓存实战:从强缓存到协商缓存
Cache-Control 到底怎么配?为什么改了代码用户还是看到旧页面?这篇按真实排查思路讲。
「我明明发了新版,为什么用户看到的是旧的?」——十有八九是缓存头没配对。HTTP 缓存不复杂,核心就是两套机制:强缓存(不发请求直接用本地)和协商缓存(发请求问服务器有没有变)。配错任何一个,要么更新不生效,要么白走流量。
强缓存:Cache-Control 与 Expires
Cache-Control 是现在的标准,Expires 是老式日期写法,两者同时存在以前者为准。常用的几个值:
- max-age=秒:缓存有效期,最核心的一个
- no-cache:不是「不缓存」,是缓存但每次都要向服务器验证
- no-store:真不缓存,敏感页面(后台、结算)才用
- public / private:private 表示只给当前用户缓存(浏览器私有缓存),CDN 不应缓存
典型配置:
# HTML:短缓存,保证更新及时
Cache-Control: public, max-age=3600
# 带指纹的静态资源:一年强缓存
Cache-Control: public, max-age=31536000, immutable带哈希文件名的资源(app.a1b2c3.js)内容变了文件名必变,可以大胆给一年;HTML 文件名不变,必须短缓存,否则发布新版用户看不到。
协商缓存:ETag 与 Last-Modified
强缓存过期后,浏览器带着凭证去问服务器。两种凭证:
- ETag:资源的指纹(内容哈希),最精确。服务器比对一致返回 304 Not Modified,告诉浏览器「没变,用本地副本」
- Last-Modified:最后修改时间,精度只到秒,同一秒内多次修改会误判
响应流程:
首次请求 → 200,响应带 ETag: "abc123"
再次请求 → 请求头带 If-None-Match: "abc123"
内容未变 → 304,无响应体,省掉整份传输
内容已变 → 200 + 新 ETagETag 优先;自建服务器(Nginx)对静态文件默认两者都开,不用手工配。
缓存层级:浏览器、CDN、服务端
一次请求可能穿过好几层缓存:
1. 浏览器内存缓存(最快,关掉标签页就没了) 2. 浏览器磁盘缓存 3. CDN 边缘节点 4. 源站反向代理(Nginx 的 proxy_cache) 5. 应用本身
排查「用户看到旧页面」时,从上往下逐层确认:Ctrl+Shift+R 强刷能好,是 1-2 层;换网络/IP 能好,是 CDN;怎么都不好,是源站缓存或应用输出缓存。
实战:发布新版本不失效、不陈旧
- 构建工具给 JS/CSS 文件名加内容哈希(Vite/webpack 默认行为),HTML 引用带哈希的新路径
- HTML 给 max-age=3600 或 no-cache;带哈希资源给一年 immutable
- Nginx 按扩展名分开配:
location ~* \.(js|css|png|webp|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location ~* \.html$ {
expires 1h;
}- 发版后做一次 CDN 全站刷新(purge),别只刷首页
三个高频踩坑
- 给 HTML 配了一年强缓存:发版三天用户还在看旧版——HTML 必须短缓存
- no-cache 当 no-store 用:以为不缓存,实际每次都发协商请求,接口压力翻倍
- CDN 回源也吃源站缓存:purge 了 CDN 但源站 Nginx 还有缓存,改完不生效,两层都要清
一句话总结:带哈希的资源往死里缓存,不带哈希的短缓存或协商缓存;发布链路里每一层缓存都要知道它存在、知道怎么清。
评论 · 0 条
还没有评论,来抢沙发。
评论经人工审核后公开;请勿提交密码、密钥等敏感信息。