你改完首页,刷新十次都是旧版?用户说页面卡顿,你查服务器一切正常——八成是浏览器缓存在“装死”。

别急着骂前端、怪 CDN,先低头看看你的 Cache-Control 配得对不对。缓存配对了,用户打开页面快得像翻微信聊天记录;配错了,你发十条新文章,用户看到的还是昨天的草稿。

你的缓存配置到底错在哪?

很多人分不清“强制缓存”和“协商缓存”。
强制缓存:浏览器自己说了算。“这个文件我存着,30天内不问你。”
协商缓存:浏览器客气一点。“我这儿有份旧文件,您看它还新鲜吗?”靠 ETagLast-Modified 跟服务器对一下,没变就返回 304,省流量又快。

最常踩的坑,就是给 HTML 页面加了长时效强制缓存。
有个资讯站朋友,首页天天更新,他却把 Cache-Control: max-age=86400 直接怼到 HTML 上。结果每次发完新文章,他自己刷得到,用户刷出来全是昨日头条。等他想起来改配置,热点早凉了。后来改成 HTML 用协商缓存,图片/CSS/JS 单独设长缓存,问题当场消失。

另一个隐形杀手:CSS 和 JS 文件名不变,内容却天天改。
比如 style.css 改了三版,但文件名没动。浏览器一看:“哎,我硬盘里有,不用下。”你调好的按钮颜色,用户永远看不到。解法就俩字:换名。style.v2.css 行,style.abc123.css 更稳——只要 HTML 里引用的路径变了,旧缓存自然作废。

3个方法精准控制静态资源的缓存时长

静态资源(图片、CSS、JS)天生适合缓存:不常改、体积大、影响首屏速度。缓存一个月?完全没问题。但乱配一气,反而拖慢更新节奏。

方法一:按后缀分类设缓存时间
Nginx 里几行就能搞定:

location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
    expires 30d;
    add_header Cache-Control "public, immutable";
}

expires 30d 让浏览器记住“这图/这脚本30天内别来问”。immutable 是个温柔的提醒:哪怕用户按 Ctrl+F5 强刷,也别发请求了——文件真不会变。我们团队上线这套配置后,二次访问的页面加载肉眼可见地“轻”了。

方法二:API 接口坚决不硬缓存
数据接口不是静态资源。用户刚下单,订单列表还显示“待支付”?那可能是你把 /api/orders 也塞进了 max-age=3600
正确做法:Cache-Control: no-cachemax-age=0。浏览器每次都会带 If-None-Match 去问一句,服务器判定了再给新数据或 304——安全又及时。

方法三:用 Service Worker 缓存骨架屏
进阶但实用。比如电商详情页,把 loading 骨架图 + 主图提前塞进 Service Worker。用户弱网打开,先看到结构,再慢慢填内容。不止体验顺滑,跳出率也降了一截——毕竟没人愿意对着白屏干等3秒。

为什么你的 HTML 页面不该用强制缓存?

HTML 是网站的“门脸”,它里面写着 <link href="style.v2.css"><script src="app.789xyz.js">
一旦 HTML 被强制缓存,后面所有资源路径都锁死了。你偷偷替换了 JS,但浏览器压根不重新拉 HTML,自然也看不到新路径,旧 JS 继续跑,bug 也在继续演。

真事:一个旅游网站上线改版,设计师换图、前端改样式、后端调接口,全齐了。唯独忘了 HTML 的缓存头还是 max-age=3600。老板拿手机一点,首页还是老配色。技术团队抓耳挠腮两小时,最后发现——删掉浏览器缓存,新版立刻出现。可那半小时的流量,已经默默流去了竞品站。

所以 HTML 的铁律只有一条:别让它长期赖在用户电脑里。
Cache-Control: no-cachemax-age=0,再配上 ETag。每次访问,浏览器照例问一句:“这 HTML 还是原来那个吗?”是,就 304;不是,立马给新的。成本几乎为零,却能守住“所见即所得”的底线。

而 CSS、JS、图片这些,放心大胆设长缓存,再配合版本号或哈希命名——路径一变,旧缓存自动失效,新内容秒达。

配置完缓存后,怎么验证它生效了?

别信配置文件,信 Chrome DevTools。

打开 Chrome,无痕模式(避开插件干扰),F12 → Network → 刷新页面。
看每行的 Size 列:
✅ 显示 (disk cache)(memory cache) —— 缓存成功
❌ 显示具体大小(如 42 KB)—— 正在重下,缓存没起作用

再点开某个 JS 文件,切到 Headers 标签页:

  • Cache-Control 值是不是你写的?比如 public, immutable, max-age=2592000
  • ETagLast-Modified 字段有没有?有,说明协商缓存已就位

顺便提醒:如果所有图片都标着 no-cache,别以为这是“严谨”,其实是“自废武功”。我们帮一个客户调优时发现,他们连 logo.png 都禁止缓存——结果每次访问都重下,首屏多耗了1秒多。改成 max-age=30d 后,加载明显轻快。

缓存没生效?检查这3个地方

配完了,但用户还是刷不出新版?先别怀疑人生,90%是这三个地方在捣鬼:

第一,CDN 把你的配置“覆盖”了
Cloudflare、阿里云 CDN、腾讯云 CDN……它们都有自己的缓存规则。你在 Nginx 写了 max-age=30d,CDN 却默认只缓存10分钟。去 CDN 控制台翻一翻「缓存规则」或「缓存过期时间」,确保和你源站一致。有位博主折腾半天,最后发现 Cloudflare 的“标准缓存”策略把所有静态资源锁死在1小时——关掉,立竿见影。

第二,浏览器插件悄悄禁用了缓存
AdGuard、uBlock Origin、某些隐私扩展会主动屏蔽缓存逻辑。测试很简单:无痕模式打开网站,再开 DevTools 看 Network。如果无痕里缓存正常,普通窗口不行——那就是插件在搞事情。

第三,HTTP 和 HTTPS 混用
哪怕只有一张图用 http:// 加载,整个页面的缓存行为都可能紊乱。现代浏览器对混合内容越来越警惕,有时直接拒用缓存。全站检查一遍,把所有 http:// 换成 https:// 或协议相对路径 //

今天就能执行的3步优化

别收藏吃灰,现在就做:

第一步:打开你正在用的 Web 服务器配置文件(Nginx 的 nginx.conf 或 Apache 的 .htaccess),找到处理图片/CSS/JS 的 location 块。加上这两行:

expires 30d;
add_header Cache-Control "public, immutable";

保存,运行 sudo nginx -t && sudo systemctl reload nginx(Nginx)或重启 Apache。

第二步:确认 HTML 页面的响应头。如果是 Nginx,在 server 或 location 块里加:

add_header Cache-Control "no-cache";
etag on;

确保 etag on; 已开启(默认通常开着),这样协商缓存才能工作。

第三步:打开 Chrome 无痕窗口,访问你的网站首页,按 F12 → Network → 刷新。找一个 CSS 文件,看 Size 是否是 (disk cache);再找 HTML 请求,看 Status 是否是 304。两个都对,恭喜,缓存已上岗。