tech

HTTP 缓存:从 Expires 到 ETag

梳理浏览器缓存的目标、强缓存与协商缓存,以及生产环境中静态资源和 HTML 的缓存策略。

缓存的核心很简单:浏览器或中间代理保存服务器返回的资源,下次请求时优先使用已经保存的数据。

这样做主要解决三个问题:

  • 减少网络延迟。一次请求可能需要几十到几百毫秒,命中缓存后可以直接从浏览器缓存读取。
  • 减少服务器压力。相同资源不需要反复生成和传输。
  • 节省带宽。静态资源不必在每次访问时重新下载。

Expires:依赖客户端时间

HTTP/1.0 时代通常使用 Expires

Expires: Wed, 30 Jul 2026 10:00:00 GMT

浏览器将当前时间与 Expires 比较,判断资源是否过期。问题在于,当前时间来自客户端自己的系统时钟。用户可以手动修改系统时间,因此缓存判断可能出现偏差。

Cache-Control:由浏览器记录缓存时间

HTTP/1.1 引入了更常用的 Cache-Control

Cache-Control: max-age=3600

max-age=3600 表示资源在缓存后 3600 秒内有效。浏览器自己记录资源写入缓存的时间,不再依赖服务器下发的绝对时间,也就避免了客户端系统时间造成的部分问题。

强缓存与协商缓存

如果缓存过期后每次都重新下载资源,大量请求仍然会同时回到服务器。因此 HTTP 缓存通常分为两种行为。

强缓存

在有效期内,浏览器直接使用本地缓存,不向服务器发送请求。常见配置是:

Cache-Control: public, max-age=3600

资源过期后,浏览器才会重新发起请求。

协商缓存

缓存过期后,浏览器先询问服务器资源是否发生变化:

  • 资源没有变化,服务器返回 304 Not Modified,浏览器继续使用本地缓存。
  • 资源发生变化,服务器返回 200 和新的资源内容。

协商缓存可以避免重复传输完整文件,但仍然需要一次请求和响应。

Last-Modified 的局限

服务器可以通过 Last-Modified 告诉浏览器资源的最后修改时间:

Last-Modified: Wed, 30 Jul 2026 10:00:00 GMT

下一次请求时,浏览器发送:

If-Modified-Since: Wed, 30 Jul 2026 10:00:00 GMT

服务器比较文件修改时间后决定返回 304 还是新内容。

它的缺点是时间精度通常只能到秒。假设资源在 10:00:00.500 发生变化,而服务器记录仍然是 10:00:00,就可能误判资源没有变化。

ETag:使用内容指纹

ETag 用资源内容生成一个指纹。只要内容发生变化,指纹就应该变化:

ETag: "abc123"

浏览器下次请求时发送:

If-None-Match: "abc123"

服务器比较当前资源的 ETag:

  • 相同:返回 304 Not Modified
  • 不同:返回 200 和新的资源内容。

因此,生产环境通常会组合使用:

Cache-Control: max-age=3600
ETag: "资源内容指纹"

Nginx 配置示例

HTML 入口文件通常不做很长时间的强缓存,让浏览器通过协商缓存及时检查新版本:

server {
    location ~* \.html$ {
        expires -1;
        add_header Cache-Control "no-cache";
    }

    location ~* \.(js|css|png|jpg|jpeg|gif|ico|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, max-age=31536000, immutable";
    }
}

带哈希文件名的静态资源,例如 app.abc123.jsstyle.def456.css,内容一旦变化文件名也会变化,因此适合缓存一年并设置 immutable。HTML 入口文件则保留较短的缓存策略,避免用户长期拿到旧版本入口。

Nginx 默认会开启 ETag(etag on),也可以根据项目需要显式配置。

实际选择

可以先记住一条简单规则:

  • HTML:短缓存或 no-cache,使用协商缓存检查版本。
  • 带内容哈希的 JS、CSS、字体和图片:public, max-age=31536000, immutable
  • 没有哈希的静态资源:使用较短的 max-age,并配合 ETag。

缓存策略不是越久越好,关键是资源 URL 是否能在内容变化时同步变化。只要文件名带有可靠的内容指纹,长缓存才不会妨碍发布新版本。