HTTP 缓存指南:无需额外基础设施即可加速您的网站
掌握 Cache-Control 头部、ETag、CDN 缓存策略和浏览器缓存行为,大幅提升网站加载速度并降低服务器成本。
缓存是您能做的杠杆效益最高的性能优化手段。缓存的响应在微秒内即可送达,零成本,且无需任何服务器处理。然而,大多数应用要么缓存过于激进(提供过期内容),要么完全不缓存(浪费带宽和计算资源)。理解 HTTP 缓存,能让它从 bug 的温床变成你的超级武器。
HTTP 缓存的工作原理
当浏览器或 CDN 接收到响应时,它会检查响应头来决定是否缓存以及缓存多长时间。下次请求同一资源时,可以直接提供缓存副本——完全不需要访问您的服务器。
缓存生命周期分为两个阶段:
- 新鲜度(Freshness) — 缓存副本是否仍然有效?由
Cache-Control: max-age或Expires决定。 - 验证(Validation) — 若已过期,能否向服务器确认内容未发生变化?由
ETag或Last-Modified决定。
Cache-Control:主要缓存指令
Cache-Control 是最强大的缓存头部,是一组以逗号分隔的指令:
Cache-Control: public, max-age=31536000, immutable
关键指令
| 指令 | 含义 |
|---|---|
public |
任何缓存(浏览器、CDN、代理)均可存储 |
private |
仅终端用户的浏览器可缓存(CDN 不可缓存) |
no-cache |
每次使用前必须向服务器重新验证(不是"不缓存") |
no-store |
完全不缓存——用于敏感数据 |
max-age=N |
缓存 N 秒 |
s-maxage=N |
CDN 专用最大缓存时长(对共享缓存覆盖 max-age) |
immutable |
内容永不变更——完全跳过重新验证 |
must-revalidate |
过期后必须重新验证才能提供 |
stale-while-revalidate=N |
在后台获取新内容期间,可继续提供过期内容最多 N 秒 |
⚠️
no-cache并不意味着"不缓存"。它的意思是"缓存它,但每次使用前都要检查是否仍然有效"。如果您真的不希望内容被缓存,请使用no-store。
按资源类型制定缓存策略
不同资源需要不同的缓存策略:
带内容哈希的静态资源(CSS、JS、图片)
Cache-Control: public, max-age=31536000, immutable
如果您的构建工具在文件名中加入了哈希值(main.a3f9b2c.js),内容变化时 URL 也会随之改变。永久缓存——任何新版本都会有新的 URL。
HTML 页面
Cache-Control: no-cache
或设置较短的 TTL:
Cache-Control: public, max-age=60, stale-while-revalidate=3600
HTML 变化频繁,且链接到带哈希的资源。建议短暂缓存或强制重新验证。
API 响应
# 公开数据(如产品目录)
Cache-Control: public, max-age=300, stale-while-revalidate=600
# 用户专属数据
Cache-Control: private, max-age=60
# 实时数据(股票价格、实时比分)
Cache-Control: no-store
敏感数据(身份验证、支付)
Cache-Control: no-store
绝对不要缓存。
ETag 与条件请求
当缓存响应过期时,浏览器不会直接丢弃它——而是询问服务器内容是否仍然有效。这就是重新验证。
ETag
ETag 是响应内容的指纹:
# 服务器发送:
ETag: "a3f9b2c8d4e1"
# 浏览器下次请求:
If-None-Match: "a3f9b2c8d4e1"
# 若内容未变,服务器响应:
HTTP/1.1 304 Not Modified
(无响应体——节省带宽)
# 若内容已变,服务器响应:
HTTP/1.1 200 OK
ETag: "b7c2d4e9a1f3"
(完整的新响应)
304 Not Modified 响应没有响应体——只有头部。这节省了重新下载内容所需的全部带宽。
Last-Modified
类似的机制,但使用时间戳而非哈希值:
Last-Modified: Tue, 01 Apr 2026 10:00:00 GMT
# 浏览器发送:
If-Modified-Since: Tue, 01 Apr 2026 10:00:00 GMT
ETag 更为可靠(在负载均衡服务器场景下,时间戳可能不够精确)。
Vary 头部:按请求变体分别缓存
Vary 头部告知缓存哪些请求头会影响响应内容:
Vary: Accept-Encoding
这会分别缓存 gzip 和 br 压缩的响应。常见用法:
Vary: Accept-Encoding # 分别缓存压缩/未压缩版本
Vary: Accept-Language # 按语言分别缓存
Vary: Accept # 分别缓存 JSON 和 HTML 响应
⚠️
Vary: Cookie或Vary: Authorization实际上会禁用 CDN 缓存——CDN 无法缓存用户专属的响应。
stale-while-revalidate:后台刷新
这是最实用的现代缓存模式之一:
Cache-Control: max-age=60, stale-while-revalidate=600
- 前 60 秒内直接从缓存即时响应(新鲜状态)
- 第 60 到 660 秒之间:立即提供过期缓存副本,同时在后台获取新版本
- 超过 660 秒后:必须重新验证后才能提供
用户始终能获得快速响应,缓存保持新鲜,且不会强迫任何人等待网络往返。
CDN 缓存注意事项
CDN(Cloudflare、CloudFront、Fastly)遵循 Cache-Control 头部,但也引入了自身的复杂性:
-
s-maxage允许您为 CDN 和浏览器设置不同的 TTL:Cache-Control: public, max-age=60, s-maxage=86400浏览器缓存 1 分钟;CDN 缓存 24 小时。
-
缓存清除 — 部署时,清除 CDN 中已更新资源的缓存。大多数 CDN 提供基于 API 的缓存清除功能。
-
缓存键 — CDN 基于 URL + Vary 头部进行缓存。查询字符串通常包含在缓存键中。
测试缓存行为
使用我们的 API Request Builder 检查响应头部,验证您的缓存配置是否正常工作:
- 发送请求并检查
Cache-Control、ETag和Last-Modified头部 - 再次发送相同请求——检查
Age头部(自缓存以来的秒数)和X-Cache: HIT - 检查
CF-Cache-Status(Cloudflare)或X-Cache(CloudFront)以确认 CDN 缓存生效
在 Chrome DevTools → Network 标签页 → 点击某个资源 → Headers 标签页——在响应中查找 from disk cache 或 from memory cache。
Nginx 缓存配置
在 Nginx 层面配置缓存头部,确保行为一致:
# 静态资源——永久缓存
location ~* \.(js|css|woff2|png|jpg|webp|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# HTML——始终重新验证
location ~* \.html$ {
add_header Cache-Control "no-cache";
}
# API——短期缓存加 stale-while-revalidate
location /api/ {
add_header Cache-Control "public, max-age=60, stale-while-revalidate=600";
}
使用我们的 Nginx Config Generator 为您的使用场景生成完整、优化的 Nginx 配置。
缓存检查清单
- 静态资源(CSS/JS)使用内容哈希文件名 +
max-age=31536000, immutable - HTML 使用
no-cache或非常短的max-age - API 响应根据更新频率进行缓存
- 敏感数据使用
no-store - 已启用 ETag 或
Last-Modified以支持条件请求 - 在适当的 API 端点上使用
stale-while-revalidate - CDN 缓存头部已验证并测试
正确配置 HTTP 缓存,能让回访用户感受到即时响应,降低带宽成本,减轻服务器负载——这一切都无需任何额外基础设施。