缓存不是一个单一开关。一个响应既有一个由缓存作出的新鲜度决策,也有与源站之间的校验对话。把这两种想法混在一起,会得到陈旧内容或多余请求。

先选变更模型

对于发布之后可能变化的 HTML 文档,Cache-Control: no-cache 通常比 no-store 更接近正确起点。no-cache 允许存储,但要求缓存复用前先校验;no-store 则要求缓存完全不存储。当你想要条件请求与廉价的 304 Not Modified 时,这个区别很关键。

对类似 app.4f2c.js 这种带指纹的资源,URL 在字节变化时也随之改变,此时一个较长的新鲜度生命周期是合理的:

Cache-Control: public, max-age=31536000, immutable

但对一个内容可能被原地替换的稳定 URL,该策略不安全。如果 URL 稳定,应优先使用较短的生命周期,并搭配实体标签(ETag)或有意义的 Last-Modified 值。

同时检查两条路径

发一次普通请求,再发一次条件请求。各服务器的具体标志可能不同,但你想要的证据是稳定的:

curl -sS -D - -o /dev/null https://www.example.test/
curl -sS -D - -o /dev/null \
  -H 'If-None-Match: "copy-a-real-etag-from-the-first-response"' \
  https://www.example.test/

不要在 runbook 里粘贴编造的验证器。从第一次响应里复制该值,或使用测试夹具。只有服务器能证明所选表示未变化时,成功的重校验才应返回 304

让中间层可见

AgeVary 与 cache-control 指令说明了为什么两个客户端可能得到不同结果。如果响应随 Accept-Encoding、语言或其他请求字段变化,该维度应进入 Vary;否则共享缓存可能复用错误的表示。

有意义的发布检查不是"扫描器找到了缓存头",而是"该头与替换策略匹配,且条件请求的行为符合文档说明"。

来源