每个 Web 开发者都应该了解的缓存基础知识
你的用户刚刚点击了一个按钮,然后三秒钟内什么都没发生。数据库查询已完成,服务器也已响应,但昨天加载过的相同数据却不得不再次穿越整个互联网传输。这就是缓存要解决的问题。
对于前端开发者来说,理解 HTTP 缓存基础知识不是可选项,而是必备技能。这是一个响应迅速的应用程序与一个感觉像坏了的应用程序之间的区别。本指南涵盖了你需要了解的浏览器缓存与 CDN 缓存、Cache-Control 头以及像 ETag 和 Last-Modified 这样的验证机制。
核心要点
- 为每个响应设置明确的
Cache-Control头,以避免不可预测的浏览器启发式行为。 - 对静态资源使用较长的
max-age值,并配合缓存破坏(内容哈希文件名)。 - 对必须保持新鲜的内容使用
no-cache配合 ETag 或 Last-Modified。 - 始终将个性化或需要身份验证的内容标记为
private,以防止数据通过共享缓存泄露。
什么是缓存以及为什么它很重要
缓存将数据副本存储在更接近需要它的地方。当你的浏览器请求一张图片时,它可以将该图片保存在本地。下次请求时就完全跳过网络传输。
性能提升是显著的。例如,数据库查询可能需要几十毫秒,而从浏览器缓存读取通常几乎是即时的,并且完全避免了网络往返。
缓存带来三个核心优势:
- 降低延迟:数据传输距离更短。
- 降低服务器负载:更少的请求到达你的源服务器。
- 更好的用户体验:重复访问时页面加载更快。
理解缓存层级
请求在到达你的服务器之前会经过多个缓存层。每一层都有不同的用途。
浏览器缓存(私有缓存)
浏览器缓存位于用户的设备上。它存储只有该用户应该看到的响应。当你将响应标记为 private 时,它会留在这里,永远不会与其他用户共享。
这是个性化内容应该存放的地方——用户配置文件、需要身份验证的 API 响应、任何与特定会话相关的内容。
CDN 缓存(共享缓存)
CDN 缓存位于用户和你的源服务器之间,分布在各个地理位置。当东京的用户请求你的 JavaScript 包时,CDN 会从附近的边缘服务器提供服务,而不是从你的源数据中心。
CDN 擅长缓存静态资源:图片、CSS、JavaScript 和字体。它们也可以缓存公共 API 响应,尽管这需要仔细配置。
服务器端缓存
除了 HTTP 缓存之外,服务器通常使用 Redis 或 Memcached 等工具维护自己的缓存。作为前端开发者,你不会直接配置这些,但了解它们的存在有助于你理解为什么某些 API 响应比其他响应更快。
Discover how at OpenReplay.com.
Cache-Control 头:核心指令
Cache-Control 头告诉缓存如何处理响应。以下是你最常使用的指令:
| 指令 | 含义 |
|---|---|
max-age=3600 | 缓存 3600 秒(1 小时) |
no-cache | 存储它,但在使用前重新验证 |
no-store | 不要在任何地方存储此响应 |
private | 只有浏览器缓存可以存储此响应 |
public | 任何缓存都可以存储此响应 |
一个常见的错误:no-cache 并不意味着”不缓存”。它意味着”在使用缓存副本之前始终与服务器检查”。当你真的想完全不缓存时,使用 no-store。
对于具有哈希文件名的静态资源,使用激进的缓存策略:
Cache-Control: public, max-age=31536000
对于频繁变化的 HTML 页面(特别是对于敏感或高度动态的内容):
Cache-Control: no-cache, private
ETag 和 Last-Modified:验证机制
当缓存内容过期时,浏览器并不总是需要重新下载所有内容。验证头启用了高效的重新验证。
Last-Modified 包含一个时间戳。在后续请求中,浏览器发送带有该时间戳的 If-Modified-Since 头。如果没有任何变化,服务器返回 304 Not Modified——没有响应体,最小带宽消耗。
ETag 包含一个唯一标识符(通常是内容哈希)。在后续请求中,浏览器发送带有该标识符的 If-None-Match 头。结果相同:如果内容未更改,返回 304。
ETag 比时间戳更精确,因为它们检测实际的内容变化,而不仅仅是文件修改时间。文件可能在没有更改内容的情况下被重新保存,更新了时间戳但没有更改 ETag。
静态资源的缓存破坏
你希望静态资源尽可能长时间地被缓存,但在部署时也需要用户获取更新。缓存破坏通过在内容更改时更改 URL 来解决这个问题。
现代构建工具会在文件名中附加内容哈希:
bundle.a1b2c3d4.js
styles.e5f6g7h8.css
当你部署新代码时,哈希值改变,URL 改变,浏览器就会获取新版本。旧的缓存文件只是自然过期。
需要避免的常见陷阱
公开缓存需要身份验证的内容:始终对用户特定数据使用 private。通过共享缓存将一个用户的数据泄露给另一个用户是严重的安全问题。
过度缓存 API 响应:动态数据需要较短的 TTL 或 no-cache。结账时的过期价格会导致实际问题。
忘记设置头:如果没有明确的 Cache-Control 头,浏览器可能会根据响应元数据应用启发式缓存,这可能导致你不希望看到的行为。
结论
缓存不是你配置一次就忘记的事情。为每个响应设置明确的 Cache-Control 头。对静态资源使用较长的 max-age 值配合内容哈希文件名。对必须保持新鲜的内容使用 no-cache 配合 ETag 或 Last-Modified,并始终将个性化内容标记为 private。在 DevTools 中检查你的头,验证你的 CDN 行为,并测试用户实际体验到的内容。
常见问题
no-cache 告诉浏览器它可以存储响应,但在使用之前必须与服务器重新验证。no-store 告诉浏览器根本不要保存响应。对银行详情等敏感数据使用 no-store,对经常变化但受益于条件重新验证的内容使用 no-cache。
打开浏览器 DevTools,转到 Network(网络)选项卡,然后选择任何请求。Response Headers(响应头)部分显示 Cache-Control、ETag 和 Last-Modified 值。也要查看 size(大小)列。如果显示 disk cache(磁盘缓存)或 memory cache(内存缓存),则响应是从浏览器缓存而不是网络提供的。
这取决于数据。公共的、很少变化的数据(如产品类别)可以使用较短的 max-age 值。用户特定或频繁变化的数据应使用带验证头的 no-cache 或 no-store。始终将需要身份验证的响应标记为 private,以防止 CDN 将一个用户的数据提供给另一个用户。
内容哈希让你可以为静态资源设置非常长的缓存生命周期,同时仍能即时交付更新。当文件内容更改时,哈希值更改,产生一个绕过任何现有缓存的新 URL。这种技术称为缓存破坏,是 Webpack、Vite 和 Rollup 等工具使用的标准方法。
Gain control over your UX
See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay. — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.