s-maxage与stale-while-revalidate需协同工作:前者定义CDN强缓存时长(如s-maxage=300),后者规定过期后仍可秒回旧内容并后台异步刷新(如stale-while-revalidate=600),二者必须同时存在、配合ETag和304响应才能实现无感更新。

要让 CDN 实现“用户无感、缓存不中断、内容及时更新”的效果,s-maxage 和 stale-while-revalidate 必须协同工作——前者管 CDN 的缓存生命周期,后者管 CDN 在过期后如何“边用旧边拉新”。它们不是替代关系,而是分层配合:s-maxage 定义 CDN 缓存多“新鲜”,stale-while-revalidate 定义 CDN 过期后还能“苟多久+怎么悄悄更新”。
s-maxage 决定 CDN 的强缓存窗口
s-maxage 专为共享缓存(如 Cloudflare、Fastly、阿里云 CDN)设计,它覆盖 max-age,明确告诉 CDN:“你最多缓存这个资源 s-maxage 秒,之后必须进入验证阶段”。这一步避免了 CDN 把 private 或用户专属响应错误地长期缓存。
- 若只设 max-age=300,CDN 可能忽略它(尤其当响应标有 private),导致未按预期缓存
- 正确做法是显式设置
Cache-Control: public, s-maxage=300, max-age=60——浏览器只缓存 60 秒(适合个性化页面),CDN 缓存 300 秒(统一服务大量用户) - s-maxage 必须搭配 public(或至少不带 private),否则 CDN 可能拒绝缓存
stale-while-revalidate 赋予 CDN “过期后继续服务+后台刷新”能力
仅靠 s-maxage,CDN 在过期瞬间就会回源,造成瞬时压力和用户延迟。加上 stale-while-revalidate,就能让 CDN 在 s-maxage 过期后,仍立即返回旧副本,并在后台异步发起条件请求(If-None-Match / If-Modified-Since)去校验和更新缓存。
- 典型组合:
Cache-Control: public, s-maxage=300, stale-while-revalidate=600
→ CDN 强缓存 5 分钟;过期后 10 分钟内,所有请求都秒回旧内容,同时后台静默刷新 - 该机制由 CDN 自行执行(如 Fastly、Cloudflare 原生支持),无需客户端 JS 或 Service Worker 参与
- 关键前提是:源站必须对带 If-None-Match 的请求正确返回 304 Not Modified(而非 200 + 新内容),否则 CDN 会替换缓存但浪费带宽
源站响应头必须完整且一致
CDN 不会“猜”逻辑,它严格依赖响应头指令和源站的协商缓存配合。漏掉任一环节,异步刷新就失效。
- 响应头必须同时包含 s-maxage 和 stale-while-revalidate,且都是整数秒值,不能加引号或单位:
Cache-Control: public, s-maxage=300, stale-while-revalidate=600 - 必须提供 ETag 或 Last-Modified,否则 CDN 后台请求无法携带有效验证头,只能发全量请求(触发 200)
- 动态内容(如首页 HTML、API JSON)最适合这套组合;静态资源(JS/CSS)建议用文件哈希+永久缓存(max-age=31536000),避免协商开销
验证是否生效:看 CDN 日志与响应行为
不能只看浏览器 Network 面板——CDN 的后台刷新请求通常不暴露给终端用户。验证要点在服务端和 CDN 控制台:
- 检查源站 access log:应看到带
If-None-Match的 GET 请求,且多数返回 304 - 查看 CDN 缓存命中率报表:stale-while-revalidate 阶段的请求仍计为 HIT(只是状态可能是 “stale” 或 “refreshing”)
- 用 curl 模拟过期后请求:
curl -I -H "Cache-Control: max-age=0" https://example.com/page,观察响应头是否含X-Cache: HIT (stale)类标识(依 CDN 厂商而异)


















