不喜欢广告? 无广告 今天

HTTP 缓存头 缓存控制、ETag 和 max-age,无需猜测

更新于

为网页开发者编写的一份关于HTTP缓存头的实用指南:Cache-Control指令实际上做了什么,ETag如何触发304重新验证,如何选择在部署中仍能保持有效的TTL,以及那些使缓存反而有害而非有益的常见错误。

HTTP 缓存头:Cache-Control、ETag 和 max-age,无需猜测 1
广告 移除?

您发送的每个HTTP响应要么被缓存,要么没有被缓存——如果您对此不加刻意考虑,浏览器会自行决定。结果通常是,对于频繁变化的内容,缓存非常激进,而对于很少变化的内容,则完全不缓存。这两种情况都会损害用户体验。

本指南为您提供正确的设置HTTP缓存头的思维模型:每个指令控制什么,ETag如何触发重新验证,以及如何选择能够经受部署变化且不会提供过时内容的TTL。

浏览器缓存的实际工作原理

当浏览器请求一个资源时,它首先检查本地缓存。如果存在一个新鲜的缓存副本,它会立即提供该副本——根本不需要网络请求。如果缓存副本可能已过期,它会发送一个 条件请求 到源服务器。源服务器要么确认资源未发生变化(304 Not Modified),要么发送完整的更新响应(200 OK)。

CDN位于这个生命周期的中间位置。它们将响应更靠近用户地理区域进行缓存,并遵循相同的HTTP缓存头——带有少数CDN特定的扩展,比如 s-maxage.

三个问题决定了缓存行为:

  • 该响应是否可以被缓存?Cache-Control: no-storeprivate
  • 它有多新鲜?max-ages-maxage
  • 如何验证过期状态? 由ETag或 Last-Modified

Cache-Control头指令控制

Cache-Control 头是声明缓存策略的主要方式。多个指令以逗号分隔。以下是每个指令的实际作用:

max-age

max-age=N 告诉缓存(浏览器和CDN)响应在多长时间内保持新鲜。一个带有 max-age=86400 的响应从接收到时起恰好24小时是新鲜的。之后,缓存必须重新验证才能再次提供该响应。

对于带有版本化文件名的静态资源(如 main.abc123.js),一年是常见的: max-age=31536000。对于HTML文档,一个较短的时间窗口——甚至完全不缓存——更安全,因为HTML文档引用了这些版本化的资源。

s-maxage

s-maxage 覆盖 max-age 仅适用于共享缓存(CDN、代理服务器)。浏览器会忽略它。这使得您可以为用户提供长时间缓存的响应,同时保持CDN边缘更新鲜。一个典型模式是 Cache-Control: public, max-age=3600, s-maxage=86400 ——浏览器缓存1小时,CDN缓存24小时。

no-cache

no-cache 并不意味着“不要缓存”。它意味着缓存必须在提供存储响应之前与源服务器重新验证,即使该响应仍然新鲜。响应被缓存,但每次使用都需要一次往返请求来验证其有效性。这适用于频繁变化但能从304响应中获得带宽节省的内容。

no-store

no-store 是唯一一个真正阻止缓存的指令。没有浏览器缓存,没有CDN缓存,也没有磁盘写入。将其用于包含敏感用户数据的响应——如银行对账单、医疗记录、令牌。不要将其作为默认设置,因为您尚未认真考虑过缓存问题。

public 和 private

public 明确允许共享缓存(CDN)缓存响应,即使请求带有 Authorization 标头中来实现。 private 限制缓存仅限于终端用户的浏览器——CDN不得缓存它。对于用户特定的认证响应, private 防止一个用户的响应被提供给另一个用户。

must-revalidate

must-revalidate 防止缓存提供过期响应,当它们无法连接到源服务器时。如果没有它,缓存可能会在网络不可用时提供过期响应。有了它,如果源服务器不可达,请求将返回504错误。适用于提供过期数据比错误更糟糕的内容。

ETags:精确重新验证

ETag是服务器为资源特定版本生成的标识符——可以将其视为响应体的指纹。服务器在响应中发送它:

ETag: "abc123def456"

当缓存副本过期时,浏览器会发送带有存储ETag的条件GET请求:

If-None-Match: "abc123def456"

如果资源未发生变化,服务器会返回 304 Not Modified 并返回空体——节省带宽同时确认新鲜度。如果资源已更改,服务器会返回 200 OK 和新的ETag。

强ETag与弱ETag

A 强ETag ("abc123")意味着响应在字节级别上完全相同。一个 弱ETag (W/"abc123")意味着响应在语义上等价,但可能在细微方面有所不同,比如空格或头部顺序。弱ETag可以更高效地生成,但不能用于范围请求。除非您有特定使用弱ETag的理由,否则请使用强ETag。

Last-Modified:较旧的替代方案

在ETag出现之前,服务器使用 Last-Modified 时间戳进行重新验证。服务器发送:

Last-Modified: Thu, 01 May 2026 12:00:00 GMT

浏览器通过:

If-Modified-Since: Thu, 01 May 2026 12:00:00 GMT

重新验证。如果资源自该时间戳以来未更改,服务器将返回304。

缺点:时间戳的精度为一秒。在同一个秒内修改两次的文件将被视为未更改。ETag能正确处理这种情况,是更优的选择。大多数现代框架同时发送两者——浏览器会使用可用的任一选项,ETag优先。

在不破坏部署的情况下实现缓存破坏

静态资产上的一个长 max-age 只有在内容变化时URL也发生变化时才安全。存在两种策略:

URL指纹法(推荐)

将文件内容的哈希值包含在文件名中: main.a1b2c3d4.js。当文件更改时,哈希值变化,URL变化,浏览器会获取新文件——完全绕过缓存。旧URL仍保留在缓存中,但一旦HTML引用了新URL,就不会再被请求。

Webpack、Vite和大多数现代打包工具会自动执行此操作。该模式允许您安全地设置 Cache-Control: public, max-age=31536000, immutable —— immutable 指令告诉浏览器即使缓存条目技术上已过期,也不必重新验证。

查询字符串(可靠性较低)

通过在URL末尾附加版本号作为查询字符串(main.js?v=1.2.3)技术上创建了不同的URL,但一些CDN和代理服务器在构建缓存键时会忽略查询字符串。路径中的URL指纹法更可靠且被普遍支持。

会损害缓存的常见错误

缓存不应被缓存的API响应

返回用户特定或时间敏感数据的JSON API应使用 Cache-Control: no-store 或至少 private, no-cache。一个常见错误是让CDN缓存类似 /api/user/profile 的响应,并向另一个用户提供其数据。如果您的API未设置 Cache-Control 头,一些CDN会使用启发式方法自行缓存它。

忘记设置 Vary: Accept-Encoding

如果您的服务器根据客户端的 Accept-Encoding 头提供压缩和未压缩版本的资源,则缓存必须为每个变体存储独立副本。如果没有 Vary: Accept-Encoding,CDN可能会缓存gzip版本并为不支持gzip的客户端提供,或反之。在压缩是条件性的时,始终设置它。

使用 no-cache 而实际想使用 no-store

开发者经常写 Cache-Control: no-cache 当他们希望完全阻止缓存时,但 no-cache 仍然会存储响应——它只是在每次使用前都需要重新验证。当您确实不希望响应在任何地方持久化时,请使用 no-store

在HTML上设置 max-age 但没有缓存破坏策略

HTML文档引用了版本化的资产。如果您用长 max-age缓存HTML,用户在部署后将不会获取到新的资产文件名——他们将继续运行旧的HTML,而该HTML引用了旧的哈希值。为HTML设置较短的TTL(或 no-cache),并将长TTL保留给HTML引用的不可变资产。

在发布前计算您的过期窗口

选择 max-age 值在您能将其可视化为实际的墙钟时间时更容易。iotools.cloud上的 HTTP 缓存 TTL / max-age 计算器 允许您以秒为单位输入TTL并查看确切的过期时间。在将值提交到服务器配置或CDN规则之前,可用于检查86400(24小时)、2592000(30天)或31536000(1年)等值的合理性。

实用的缓存策略检查清单

  • HTML文档: Cache-Control: no-cache ——始终重新验证,当内容未改变时受益于304响应
  • 带有哈希值在文件名中的版本化静态资产(JS、CSS、图片): Cache-Control: public, max-age=31536000, immutable
  • 未版本化的静态资产(字体、图标): Cache-Control: public, max-age=604800 (1周)
  • 公共API响应(时间敏感): Cache-Control: public, max-age=60, s-maxage=300 ——浏览器短TTL,CDN长TTL
  • 用户特定的API响应: Cache-Control: private, no-cache
  • 敏感数据: Cache-Control: no-store
  • 始终设置 Vary: Accept-Encoding 当提供条件性压缩响应时

ETags应默认开启,用于所有被缓存的内容——它们是实现高效带宽重新验证的机制。大多数Web服务器(Nginx、Apache、Caddy)在未禁用的情况下会自动生成ETags。

想要享受无广告的体验吗? 立即无广告

安装我们的扩展

将 IO 工具添加到您最喜欢的浏览器,以便即时访问和更快地搜索

添加 Chrome 扩展程序 添加 边缘延伸 添加 Firefox 扩展 添加 Opera 扩展

记分板已到达!

记分板 是一种有趣的跟踪您游戏的方式,所有数据都存储在您的浏览器中。更多功能即将推出!

广告 移除?
广告 移除?
广告 移除?

新闻角 包含技术亮点

参与其中

帮助我们继续提供有价值的免费工具

给我买杯咖啡
广告 移除?