浏览器缓存是提升网页加载速度的关键;本教程从原理、分类、HTTP 头、Service Worker、调试与实战配置等方面逐步讲解,教你在开发与运维中合理设置缓存策略,权衡性能与一致性,避免常见陷阱,并给出典型示例与排查流程,方便在静态资源、API 与 SPA 等场景中作出最佳决策。兼顾移动端与安全性。


为什么要关心浏览器缓存(用最简单的话说)
想象一下,你的网页像一本书,第一次打开要从书店买回来(下载全部资源),第二次如果书在家里(缓存),就立刻能读。浏览器缓存就是把“书”留在用户那儿,让后续访问更快、带宽更省、服务器压力更低。但也有副作用:如果书变了,用户可能拿到的是旧版。
核心概念:缓存的几种类型和关键因素
浏览器端常见缓存类型
- 强缓存(Freshness):浏览器直接使用本地缓存,不向服务器验证。依靠 Cache-Control 或 Expires。
- 协商缓存(Validation):浏览器先询问服务器资源是否更新,使用 If-Modified-Since / If-None-Match,服务器返回 304 或新内容。
- 内存缓存 与 磁盘缓存:内存快但短暂,磁盘持久但读取稍慢,浏览器根据资源大小、类型决定存放位置。
- Service Worker 缓存:由开发者通过脚本控制,适合离线或精细化缓存策略。
重要 HTTP 头及其语义
- Cache-Control: 当前主流且优先级高,常见指令有 max-age, no-cache, no-store, must-revalidate, public, private。
- Expires: 旧方案,指定绝对过期时间,HTTP/1.1 推荐 Cache-Control 优先。
- ETag: 资源指纹,协商缓存时服务器通过对比 ETag 决定是否返回 304。
- Last-Modified: 资源的最后修改时间,作为较粗糙的协商缓存依据。
如何选择缓存策略(按资源类型)
不同资源应采用不同策略,这里给出常见分类和推荐做法,便于快速决策。
静态资源(图片、字体、JS、CSS)
- 大多数可设置长期缓存,例如 Cache-Control: public, max-age=31536000, immutable,并使用版本号/哈希(如 file.abc123.js)来实现更新时立即生效。
- 如果无法使用文件名哈希,可采用短期缓存并配合强制刷新机制。
API 响应(动态数据)
- 通常需短期缓存或不缓存:Cache-Control: private, max-age=0, must-revalidate 或直接 no-cache(允许协商缓存)。
- 对于可缓存的公共 API,请考虑 ETag 或短 max-age,并结合服务端处理 304 减少带宽。
单页应用(SPA)
- HTML 主文档应设置短缓存(甚至不缓存),以避免用户长期加载旧版应用。静态资源(bundle、chunk)用长缓存并带哈希。
- Service Worker 可做更复杂的离线策略:prefetch 静态资源、network-first 或 stale-while-revalidate 等。
具体 HTTP 头实例(直观表格)
| 场景 | 示例头 | 说明 |
| 长期缓存(静态资源) | Cache-Control: public, max-age=31536000, immutable | 资源一年内不改变,浏览器直接使用本地缓存 |
| 短期缓存(API) | Cache-Control: private, max-age=60 | 适用于频繁更新但短时间可缓存的数据 |
| 强制验证 | Cache-Control: no-cache | 每次都向服务器验证,适合敏感或频繁变动的内容 |
| 完全不缓存 | Cache-Control: no-store | 不在任何地方存储,适合银行、隐私数据 |
服务器配置示例(实战)
Nginx:静态文件长缓存
在 nginx 配置中把静态目录的响应头设好就行,示例配置(请根据实际环境放到合适的 location):
location /static/ { expires 365d; add_header Cache-Control “public, max-age=31536000, immutable”; }
Nginx:API 设置协商缓存
对于 API,建议返回 ETag 并合理设置缓存头:
add_header Cache-Control “private, max-age=60, must-revalidate”;
Apache(.htaccess)示例
常见的静态缓存设置:
ExpiresActive On
ExpiresByType image/png “access plus 1 year”
Header set Cache-Control “public, max-age=31536000, immutable”
Service Worker:更细颗粒度的控制
Service Worker 可以拦截请求并决定用缓存、网络还是两者结合。常见策略:
- Cache First:先查缓存,若没有再请求网络。适合图片、字体。
- Network First:先请求网络,失败再用缓存。适合 API 或动态内容。
- Stale-While-Revalidate:返回缓存同时后台拉取更新并替换缓存,用户体验好且兼顾更新。
简单示例(伪代码思路):在 fetch 事件里尝试 caches.match(request),若存在就返回,同时在后台 fetch 最新并用 caches.put 更新。嗯,就是这么直白。
调试与排查流程(Chrome 为例)
- 打开 DevTools(F12),切换到 Network 面板,勾选 Disable cache(注意:只有打开 DevTools 时有效)。
- 查看请求的 Response Headers:检查 Cache-Control、Expires、ETag、Last-Modified。
- 观察 Status:200 表示返回新内容,304 表示协商缓存命中但服务器未返回新内容。
- 利用 Application 面板查看 Cache Storage(Service Worker)、Storage 下的浏览器缓存。
- 如果不生效,注意中间代理(CDN、反向代理)可能会覆盖头,需要在这些层面也配置。
常见问题与应对(真接地气的那种)
1. 更新后用户仍看到旧资源
原因:使用长期缓存但未改变资源名。解决:引入文件名哈希(content hash),或在 HTML 中引用版本号查询字符串(不推荐长期用 querystring 管理静态文件)。
2. ETag 导致每次都验证但仍返回 200
可能是服务端没有正确生成 ETag 或计算逻辑每次都改变(比如包含时间戳)。检查生成方式,确保只有内容改变时 ETag 才变。
3. CDN 缓存与浏览器缓存冲突
CDN 在源站与用户之间,可能会缓存并返回与源站不同的头。排查时直接请求源站(绕过 CDN)或查询 CDN 控制台缓存策略,调整 Cache-Control 或设置 Cache-Key。
性能与一致性的权衡(别只看速度)
缓存能提升速度,但过度缓存会降低内容一致性,有时可能影响功能(比如用户权限变更后看到旧页面)。因此:
- 对关键页面(登录态、个人中心)使用 no-store 或 private。
- 对静态资源使用长期缓存 + 哈希,确保可缓存性与可控更新。
- 对 API 使用短时缓存或协商缓存,结合 ETag/Last-Modified 降低带宽。
实战小贴士(我平时会这么做)
- 构建流程中加入静态资源哈希(webpack、rollup 等支持),部署后可放心将这些文件设置为长期缓存。
- HTML 主文件设置短缓存甚至不缓存,CDN 层用 stale-while-revalidate 策略可以在保证可用性的同时平滑更新。
- 在开发阶段频繁遇到缓存问题,先在浏览器中勾选“Disable cache”,确认问题不是缓存引起的。
- 在后端统一输出缓存头,避免在不同服务或微服务之间出现不一致。
快速排查清单(可复制到你的笔记里)
- 检查响应头:Cache-Control、Expires、ETag、Last-Modified。
- 是否使用文件名/哈希?若没有,考虑增加。
- CDN/代理是否覆盖了头?尝试直接请求源站。
- Service Worker 是否拦截并返回旧缓存?考虑 unregister 并重试。
- 调试浏览器 DevTools 的 Network 与 Application 面板。
一些典型配置示例(便于复制粘贴)
| 用途 | 示例头 |
| 静态资源长期缓存 | Cache-Control: public, max-age=31536000, immutable |
| HTML 主文档 | Cache-Control: no-cache, must-revalidate |
| 敏感数据 | Cache-Control: no-store |
最后说两句(随手记录的体会)
缓存听起来复杂,但本质是“什么时候可以让用户用旧东西,什么时候必须给他最新东西”。把资源分级、用哈希、合理设置头,加上 DevTools 调试,大多数问题就能迎刃而解。嗯,平常我会先把静态资源哈希做好,再看 HTML 的缓存策略;遇到缓存问题,先怀疑 Service Worker 和 CDN。就这样,慢慢调准,你的网站会既快又靠谱。