前端 CDN 缓存策略调优:静态资源、HTML 与 API 分层设计(续篇)
CDN 缓存策略一刀切?静态资源缓存一年没问题,HTML 缓存一年就炸了。
一、场景痛点
你的前端应用部署后,CDN 配了统一的 Cache-Control: max-age=86400(1 天缓存)。静态资源(JS/CSS)1 天缓存没问题——文件名包含 contenthash,内容变了文件名也变。但 HTML 也被缓存了 1 天,新版本上线后用户看到的是旧 HTML,旧 HTML 引用旧 JS,新版本永远不会被加载。
你把 HTML 的缓存改成 no-cache,但 CDN 的 no-cache 行为不一致——有些 CDN 把 no-cache 当成"不缓存",有些当成"缓存但每次验证"。你需要在 CDN 配置层面把静态资源和 HTML 分开:静态资源长缓存,HTML 短缓存或不缓存。
核心矛盾:CDN 缓存策略必须分层:静态资源缓存 1 年,HTML 缓存 5 分钟或不缓存,API 不缓存。一层策略不能覆盖三种资源类型。
二、底层机制与原理剖析
2.1 三种资源类型的缓存需求
2.2 CDN 的 Cache-Control 行为差异
不同 CDN 对 Cache-Control 的解释不同:
| max-age=31536000 | 缓存1年 ✅ | 缓存1年 ✅ | 缓存1年 ✅ |
| max-age=0, no-cache | 每次回源验证 | 每次回源验证 | 可能缓存(需配回源) |
| no-store | 不缓存 | 不缓存 | 不缓存 ✅ |
| stale-while-revalidate=300 | 支持SWR | 支持SWR | 不支持SWR |
关键:no-cache 在部分 CDN 上不是"不缓存"而是"缓存但每次验证"。如果你的 CDN 不支持 SWR,HTML 的最佳策略是 max-age=300(5 分钟缓存)而不是 no-cache。
三、生产级代码实现
3.1 Nginx 分层缓存配置
# nginx-cdn.conf —— 分层缓存策略配置
# 静态资源:长期缓存 + immutable
location /assets/ {
root /usr/share/nginx/html;
# 一年缓存 + immutable:文件名含 contenthash,内容永远不变
add_header Cache-Control "public, max-age=31536000, immutable";
etag off; # 不需要 ETag:文件名已经是唯一标识
gzip on;
}
# HTML 入口:短缓存 + stale-while-revalidate
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
# 5 分钟缓存 + SWR:用户拿到缓存版本(快),后台验证更新
# 如果 CDN 不支持 SWR,退化为 max-age=300
add_header Cache-Control "public, max-age=300, stale-while-revalidate=300";
etag on; # ETag 辅助验证:HTML 内容没变时返回 304
}
# API 请求:不缓存(通过 Nginx 代理转发到后端)
location /api/ {
proxy_pass http://backend-service:8080;
# API 数据动态,不缓存
add_header Cache-Control "no-store, no-cache, must-revalidate";
# 代理缓存也关闭:Nginx 不缓存后端响应
proxy_cache off;
proxy_no_cache 1;
}
# Service Worker:不缓存(每次必须拿到最新版)
location /sw.js {
root /usr/share/nginx/html;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
3.2 CDN 主动 purge 策略
# cdn_purge_strategy.py —— 部署后主动 purge HTML 缓存
import logging
import requests
import time
logger = logging.getLogger('cdn-purge')
class CDNPurgeManager:
"""部署后主动 purge HTML 缓存,不 purge 静态资源"""
def purge_after_deploy(self, domain: str, api_key: str):
"""部署完成后 purge HTML 的 CDN 缓存"""
# 只 purge HTML:静态资源靠 contenthash 自然更新
# 如果 purge 全站,静态资源的缓存也失效了(损失巨大)
urls_to_purge = [
f'https://{domain}/',
f'https://{domain}/index.html',
]
for url in urls_to_purge:
try:
response = requests.post(
'https://api.cdn-provider.com/purge',
headers={'Authorization': f'Bearer {api_key}'},
json={'urls': [url]},
timeout=10,
)
if response.status_code == 200:
logger.info(f'Purged CDN cache for: {url}')
else:
logger.warning(f'Purge failed: {response.status_code}')
except requests.Timeout:
# CDN purge 超时不阻断部署:缓存会在 max-age 到期后自然更新
logger.warning(f'CDN purge timeout for {url}')
# 验证:部署后 5 秒检查新版本是否可访问
time.sleep(5)
self._verify_new_version(domain)
def _verify_new_version(self, domain: str):
"""验证新版本是否在 CDN 上可见"""
try:
response = requests.get(
f'https://{domain}/',
headers={'Cache-Control': 'no-cache'}, # 绕过本地缓存
timeout=10,
)
# 检查 HTML 中是否包含新版本的特征
# 比如新版本号或新的 JS 文件名
logger.info(f'Verification response status: {response.status_code}')
except Exception as e:
logger.warning(f'Verification failed: {e}')
四、边界分析与架构权衡
4.1 SWR 的用户体验代价
SWR 模式下,用户第一次访问拿到旧版本(缓存),后台验证发现新版本后,第二次访问才拿到新版本。这意味着大约 5 分钟内,部分用户看到旧版本。
对策:如果业务不能容忍 5 分钟延迟(比如紧急修复),部署后主动 purge HTML 的 CDN 缓存。
4.2 API 缓存的特殊场景
某些 API 数据可以短期缓存:产品列表(每小时更新)、配置信息(不频繁变更)。这些 API 可以设 max-age=300 + stale-while-revalidate=3600,减少后端压力。
4.3 适用边界与禁用场景
- 适用:CDN 加速的前端应用、静态资源与 HTML 分离部署、需要不同缓存策略的多资源类型
- 禁用:纯 SSR 应用(没有静态资源)、内网应用(没有 CDN)、所有资源类型相同的应用
五、结语
CDN 缓存策略必须分层:静态资源缓存 1 年+immutable(文件名含 contenthash 保证更新),HTML 缓存 5 分钟+SWR(短期缓存+后台验证更新),API 不缓存(动态数据每次回源)。分层的关键是 Nginx 按路径分别配置 Cache-Control。部署后主动 purge HTML 的 CDN 缓存(不 purge 静态资源),保证新版本在 5 分钟内对所有用户可见。no-cache 在部分 CDN 上行为不一致,HTML 用 max-age=300 更可靠。API 的特殊场景(产品列表等)可以短期缓存,减少后端压力。
资料说明
本文中的协议、版本、性能、成本和行业趋势应以可核验的一手资料为准。未标注统计口径的比例、时间表和预测仅作工程讨论,不应视为行业事实。可参考 0730 资料来源索引,并在发布前将具体来源贴到对应断言之后。




