强缓存与协商缓存

深入理解强缓存和协商缓存的区别、Cache-Control 的各个指令、以及在不同场景下如何选择最优的缓存策略。


强缓存

强缓存不走服务器,浏览器直接使用本地缓存。

Cache-Control

Cache-Control: public, max-age=3600

常用指令

  • max-age=<seconds>:缓存有效期(秒)
  • public:可被 CDN、代理服务器、浏览器缓存
  • private:只能被浏览器缓存
  • no-cache:不直接使用缓存,每次都要和服务器验证
  • no-store:完全不缓存

Expires

Expires: Wed, 21 Oct 2025 07:28:00 GMT

HTTP/1.0 的产物,已过时,通常和 Cache-Control 同时使用作为向后兼容。


协商缓存

强缓存过期后,浏览器发起请求,服务器决定是否使用缓存。

Last-Modified / If-Modified-Since

# 服务器响应
Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT

# 浏览器下次请求
If-Modified-Since: Wed, 21 Oct 2025 07:28:00 GMT

服务器检查资源是否在 If-Modified-Since 之后有变化:

  • 无变化:返回 304 Not Modified
  • 有变化:返回 200 OK + 新内容

ETag / If-None-Match

# 服务器响应
ETag: "33a64df551425fcc55e4d42a148795d9"

# 浏览器下次请求
If-None-Match: "33a64df551425fcc55e4d42a148795d9"

ETag 是资源的唯一标识(通常用 hash),比 Last-Modified 更精确。


常见缓存场景

场景一:静态资源长期缓存

# CSS, JS, 图片
Cache-Control: public, max-age=31536000, immutable
# 一年缓存,内容变化时 URL 变化

场景二:HTML 每次验证

Cache-Control: no-cache
ETag: "abc123"
# 每次都要验证,但验证通过后可以用缓存

场景三:用户相关数据不缓存

Cache-Control: no-store, private
# 敏感数据完全不缓存

实际项目配置

Nginx

# 静态资源
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
  expires 1y;
  add_header Cache-Control "public, immutable";
}

# HTML
location ~* \.html$ {
  expires -1;
  add_header Cache-Control "no-cache, no-store";
}

Express

app.use('/static', express.static('public', {
  maxAge: '1y',
  setHeaders: (res, path) => {
    if (path.endsWith('.html')) {
      res.setHeader('Cache-Control', 'no-cache');
    }
  }
}));

缓存失效的排查

常见问题

  1. 强缓存生效但需要更新:内容变了但用户看到旧内容

    解决:内容变化时改变 URL(加 hash)

  2. 协商缓存每次都返回 200:缓存生效但每次都发请求

    解决:检查 ETag/Last-Modified 是否正确设置

  3. Cache-Control 配置错误no-cacheno-store 混淆

    • no-cache:每次验证但可以用缓存
    • no-store:完全不缓存

这一章想说的

强缓存和协商缓存的区别:

  • 强缓存:不看服务器,直接用本地缓存
  • 协商缓存:问服务器资源是否变化

最佳实践:

  1. 静态资源:强缓存 + 内容版本化
  2. HTML:协商缓存(no-cache)
  3. 用户数据:不缓存(no-store)

延展阅读