隐形层:精通 HTTP 缓存(第 2 部)
Source: Dev.to
如果你曾经大喊:“我已经在数据库里更新了数据,但用户仍然看到旧的版本!”并且疯狂地强制刷新浏览器,你就遇到了 HTTP 缓存。
当你调用 fetch('/api/user') 时,浏览器不会立刻去网络请求,而是会执行一套严格的检查清单:
- Memory/Disk Check – 我本地是否已有该资源的副本?
- Expiration Check – 如果有,本地副本是否仍然新鲜(基于
max-age)? - Short Circuit – 如果仍然新鲜,浏览器会立即返回数据,根本不与服务器通信。
这些步骤由 Cache‑Control 响应头控制。
Cache‑Control
最重要的网页性能响应头。它告诉浏览器该如何行为。
max-age(计时器)
Cache-Control: max-age=3600
数据在 1 小时(3600 秒)内保持新鲜。浏览器在此时间到期前不会再次向服务器请求。
陷阱: 如果你在 5 分钟 后部署了关键的 bug 修复,使用缓存版本的用户将在接下来的 55 分钟 内看不到该修复。
no-cache 与 no-store(常见混淆)
| 指令 | 含义 | 常见使用场景 |
|---|---|---|
no-store | 永不保存此响应。 | 敏感数据(例如银行信息)或每毫秒都会变化的数据。 |
no-cache | 保存响应,但在使用前需向服务器重新验证。 | 每次请求都需要最新版本的情况。 |
注意:
no-cache会强制浏览器在每次请求时询问服务器:“这个版本仍然有效吗?”
参考资料: MDN Web Docs – Cache‑Control
ETags 和 Last‑Modified(304 Not Modified)
在处理大型资源(例如,2 MB、包含 5,000 个商品的列表)时,你不想每次都下载整个文件。
- 首次请求: 服务器发送数据 以及 一个
ETag(唯一的哈希/指纹)。 - 后续请求: 浏览器在
If-None-Match中把ETag发送回去。 - 服务器响应: 如果哈希匹配,服务器返回 304 Not Modified。
结果: 浏览器复用缓存的版本,节省带宽和时间。
Stale‑While‑Revalidate(过期‑重新验证)
Cache-Control: max-age=60, stale-while-revalidate=600
- 新鲜(≤ 60 秒): 直接从缓存即时返回。
- 过期(60 秒 – 600 秒): 立即返回已过期的响应,同时在后台获取最新副本并更新缓存。
这样可以消除加载指示器,同时保持数据相对新鲜。
深入了解: web.dev – 爱你的缓存(stale‑while‑revalidate)
为什么这对 React 开发者很重要
你可能会想,“我只是前端开发者,根本不配置服务器头!”
现实是,你 无法用 JavaScript 修复因 HTTP 造成的问题。
- 如果 API 返回
Cache-Control: no-store,像 React Query(TanStack Query)这样的库就无法维护有效的客户端缓存,因为浏览器拒绝存储响应。 - 如果 API 为用户资料返回
max-age=31536000(1 年),用户在缓存过期之前永远看不到资料的更新。
操作: 在 Chrome DevTools → Network(网络)标签页检查响应头。浏览器的“收据”系统(条件请求)会在资源未改变时返回 304,从而避免不必要的下载。
用户体验收益
- Stale‑while‑revalidate 能在后台获取新数据的同时立即显示旧数据,省去加载动画的需求。
- 正确的缓存头可以降低延迟、减少带宽消耗,并提升感知性能。
深入资源
- MDN HTTP Caching Guide – 关于浏览器如何处理存储的综合手册。
- Google Web.dev Guide – 关于配置头部以提升性能和 Lighthouse 分数的实用指南。
- Cloudflare CDN Learning Center – 了解边缘缓存如何与浏览器缓存交互。
接下来是什么?
现在我们已经了解了不可见层,可以转向 Application Layer。在 Part 3 中,我们将探讨 React Query (TanStack Query) 并了解如何使用它实现高效的缓存系统。
在 Part 3 见。