浏览器为什么打开页面快
第二次访问同一个网站往往比第一次快很多,秘密是缓存。浏览器把响应过的资源按规则存起来,下次请求直接复用,省掉网络传输。HTTP 缓存分两类:强缓存,命中后完全不发请求直接用本地副本;协商缓存,带着标记问服务器,没变化就返回 304 继续用本地副本。
- 强缓存Cache-Control 控制,命中不发请求。
- 协商缓存ETag 与 Last-Modified 确认版本。
- 指纹文件名带哈希,改版自然失效。
- 分层HTML 不缓存、静态资源长缓存。
强缓存:Cache-Control
Cache-Control 是强缓存的主控头,max-age 告诉浏览器缓存多少秒,public 表示允许中间缓存共享,immutable 表示内容不会变。静态资源配一年缓存很常见;HTML 这类会变的页面不要长缓存,否则用户看到的是旧页面。
Cache-Control: public, max-age=31536000, immutable
Expires 是老协议的绝对过期时间,现在基本被 Cache-Control 取代;两者同时存在时 Cache-Control 优先。
协商缓存:ETag 与 Last-Modified
资源不确定有没有变时用协商缓存:服务器返回时带 ETag(内容哈希)或 Last-Modified(最后修改时间),浏览器再请求时带上 If-None-Match 或 If-Modified-Since,服务器比较后没变就回 304,浏览器继续用缓存,只有响应头没有响应体,省流量。
响应头:ETag: "abc123"
再次请求:If-None-Match: "abc123"
命中返回:HTTP/1.1 304 Not Modified
ETag 比 Last-Modified 精确,内容变才会变;两者同时存在时 ETag 优先。协商缓存每次仍有一次网络往返,但比全量下载轻得多。
优先级与启发式缓存
缓存头的优先级从高到低大致是:Cache-Control 的 no-store 最强,禁止任何缓存;然后 no-cache 表示可缓存但要协商;接着 max-age 强缓存;再往后是 Expires 与 Last-Modified。没有显式缓存头的响应,浏览器会按启发式规则估计缓存时间,常见算法是取 Last-Modified 距今的十分之一,这会让旧资源缓存很久,所以关键响应要显式声明缓存策略。
// 敏感数据禁止缓存
Cache-Control: no-store
API 响应按业务定策略:个人数据 no-store,公开数据短缓存,静态资源长缓存,口径清晰不靠猜。
缓存策略设计
标准分层:HTML 不缓存或 no-cache,保证用户拿到最新页面;JS、CSS、图片等静态资源文件名带内容哈希,配一年强缓存,改版换文件名自然失效;字体、图标同理。这样页面更新靠 HTML 变化拉新,资源更新靠文件名变化,两者不冲突。
静态资源:/assets/app-a1b2c3.js
HTML 响应:Cache-Control: no-cache
CDN 层缓存静态资源会再快一步,但要保持与源站缓存口径一致,避免 CDN 缓存了不该缓存的东西。
缓存失效与调试
缓存问题是最常见的线上疑案:用户说看到旧页面,你这边明明改过了。调试思路:先看响应头确认缓存策略,再想是不是强缓存没失效、CDN 缓存没刷新、还是 Service Worker 层缓存干扰。开发者工具里勾选禁用缓存只能排查自己,用户侧的旧缓存要等自然过期或靠文件名变化解决。
curl -I https://example.com/assets/app-a1b2c3.js
# 看 Cache-Control 与 ETag 是否合理
线上排查缓存问题先加版本参数或改文件名临时绕过,再定位根因修配置;缓存问题要在测试环境复现,别在线上反复试探。
落地清单
- HTML 用 no-cache,静态资源带指纹长缓存。
- 敏感响应显式 no-store,不靠启发式缓存。
- ETag 与 Last-Modified 给可变资源做协商缓存。
- 文件内容变就换文件名,别清缓存硬刷。
- CDN 缓存口径与源站保持一致。
- 缓存问题先看响应头,再逐层排查。