引言“我明明已经部署了最新版本为什么用户看到的还是旧页面”凌晨两点运维小哥打电话给我的时候声音里带着一丝绝望。就在十分钟前我们刚修复了一个线上紧急 Bug紧急上线紧急通知所有用户强制刷新。然而群里依然有人截图反馈“这个 Bug 还在啊”我打开自己电脑的隐身模式访问页面——一切正常。于是我得出一个自信的结论“用户肯定没刷新。”直到我亲自跑到产品经理的电脑前看着她疯狂按Cmd R至少五遍页面还是老样子。我颤颤巍巍地打开 DevTools 的 Network 面板发现所有请求都写着同一个单词(disk cache)。那一瞬间我明白了浏览器才是真正的“时间旅行者”——它把旧代码藏在硬盘里不管你怎么刷新它都坚持给你看“过去”。今天我们就来揭开浏览器缓存的神秘面纱。它到底是我们的好帮手还是偷偷捣乱的“顽固分子”一、缓存不是 bug是特性但别让它变成特性浏览器缓存的设计初衷是好的减少网络请求加快页面加载节约流量。但当我们更新代码后它就成了最大的“拦路虎”。缓存主要分两种强缓存和协商缓存。1.1 强缓存浏览器“死心眼”模式强缓存指的是浏览器在缓存有效期内根本不向服务器发请求直接使用本地副本。判断依据是响应头里的两个字段Expires一个绝对时间HTTP/1.0比如Expires: Wed, 22 Mar 2026 10:00:00 GMT。在这个时间之前浏览器都不会重新请求。Cache-ControlHTTP/1.1 引入的更强大的控制比如Cache-Control: max-age3600表示资源在 3600 秒内直接使用缓存。如果两者同时存在Cache-Control优先级更高。强缓存生效时Network 面板里会看到状态码200但 Size 一栏会显示(memory cache)或(disk cache)意味着根本没走网络。1.2 协商缓存跟服务器“商量一下”协商缓存是浏览器先去服务器问问“我这个缓存还能用吗”如果服务器说能用304浏览器就用缓存如果说不能就返回新内容200。协商缓存主要通过两对头实现Last-Modified/If-Modified-Since服务器返回资源时带上Last-Modified最后修改时间。下次请求时浏览器带上If-Modified-Since: 上次修改时间。服务器对比时间若未修改则返回 304。ETag/If-None-MatchETag 是资源的唯一标识通常是内容哈希。浏览器下次请求时带上If-None-Match: 之前的ETag服务器比对 ETag 是否一致决定返回 304 还是新内容。ETag 比 Last-Modified 更精确因为时间可能变化但内容未变或者时间未变但内容变了比如文件被覆盖。二、为什么你明明更新了用户还是旧的常见的坑有这几个坑1HTML 被强缓存了如果你的index.html被设置了Cache-Control: max-age3600那么用户在这一小时内访问浏览器都不会去服务器拿新的 HTML。而 HTML 里引用的 JS、CSS 文件又用了带哈希的文件名如main.a1b2c3.js那么即使 JS 更新了用户拿到的还是旧的 HTML旧 HTML 里引用的还是旧的 JS 路径。解决方案HTML 文件绝对不能强缓存通常设置为Cache-Control: no-cache每次都协商缓存或者Cache-Control: max-age0, must-revalidate。同时JS、CSS 等资源使用文件名哈希content hash内容变化时文件名变化这样它们可以被强缓存但版本更新时自动失效。坑2服务器时间不对Expires和Last-Modified依赖服务器时间。如果服务器时间不准可能导致缓存提前失效或过期不更新。解决方案尽量使用Cache-Control: max-age替代Expires因为它不依赖绝对时间。坑3CDN 缓存没刷新即使你的源服务器更新了CDN 节点可能还缓存着旧资源。很多 CDN 需要手动刷新或设置合理的缓存策略。解决方案部署时自动调用 CDN 刷新 API或者给资源文件名加上 hash这样新资源 URL 不同CDN 自然就会回源。坑4用户侧强制刷新真的“强制”吗用户按Cmd R或F5时浏览器会带上Cache-Control: max-age0告诉服务器“我不想要强缓存”但协商缓存仍然可能生效。如果服务器返回 304用户看到的还是旧资源。真正的“硬刷新”是Cmd Shift R或Ctrl F5它会忽略所有缓存强制请求新资源。但你不能指望用户每次都知道这个操作。三、实战如何设计一个“安全”的缓存策略一个经典的、经过无数大厂验证的缓存方案是HTML 文件Cache-Control: no-cache每次都向服务器验证但服务器可以返回 304 节省带宽。或者直接Cache-Control: no-store完全不缓存但会损失性能。CSS、JS、图片等静态资源Cache-Control: max-age31536000, immutable一年强缓存配合文件名哈希如main.9a8b7c.js。内容一变文件名变用户自然请求新资源。字体文件同静态资源。API 数据根据业务决定通常Cache-Control: no-cache或短时间max-age60。这样既保证了更新能及时生效又最大化利用缓存性能。四、如何验证缓存是否生效打开 Chrome DevTools → Network 面板。刷新页面看某个资源的Size列如果是(memory cache)或(disk cache)说明命中了强缓存。如果状态码是304说明命中了协商缓存。勾选Disable cache可以临时关闭所有缓存方便调试仅对当前 DevTools 生效。查看响应头Cache-Control、Expires、Last-Modified、ETag是否按预期设置。五、那些年我们被缓存坑过的往事场景一修改了 CSS上线后发现样式没变。打开 Network 一看CSS 文件是(disk cache)原因是文件名没变且Cache-Control: max-age604800。从此学会给文件名加 hash。场景二部署了新版但某个老用户反馈页面错乱。排查发现他的浏览器一直缓存的旧 HTML 里引用了已删除的旧 JS 文件。从那以后我强制要求 HTML 不得强缓存。场景三公司官网换了个 LogoCDN 上的图片却还是旧的。最后发现 CDN 缓存了 7 天而运维没做刷新。解决方案部署时自动清除 CDN 缓存。六、总结与缓存做朋友而不是敌人浏览器缓存不是恶魔它是我们优化性能的利器。只是我们需要理解它的脾气用正确的姿势驯服它静态资源用内容哈希 长期强缓存。入口 HTML 用协商缓存或不缓存。重要更新时确保文件名变化而不是依赖用户手动刷新。善用 Chrome DevTools 验证缓存策略。下次当你更新代码后发现用户还在用旧版本先别急着骂“他们不刷新”——很可能是你的缓存策略在偷偷作祟。每日一问你在项目里遇到过最奇葩的缓存问题是什么是图片死活不更新还是 API 数据被 CDN 缓存了三天评论区聊聊你的“缓存历险记”本文所述故事均为真实改编如有雷同说明你也该检查一下你的 Cache-Control 了。