先分清三类资源
静态站点看似只有 HTML、CSS、JS 和图片,但从缓存角度看应分成三类:入口 HTML、带指纹的构建产物、几乎不变的公开媒体。它们的更新频率和缓存策略完全不同。
| 资源 | 示例 | 建议缓存 | 更新方式 |
|---|---|---|---|
| 入口文档 | index.html、404.html | 短缓存或不缓存 | 部署后主动刷新 |
| 指纹资源 | app.a1b2c3.css | 一年 | 文件名变化 |
| 公开媒体 | 封面图、图标、字体 | 中长期缓存 | 版本目录或文件名 |
入口 HTML 引用带指纹的 CSS 和 JS,HTML 本身保持短缓存,就能兼顾更新速度和资源长期复用。
对象存储的第一原则:默认私有
除非整个桶就是公开只读资产,否则不要直接开放桶级公开访问。更稳妥的方式是限制公开目录、关闭不必要的写入权限、开启版本控制和访问日志,并定期检查桶策略。
# 检查一个公开对象的响应头 curl -I https://assets.example.com/images/cover-v2.webp # 关注这些字段 # Cache-Control: public, max-age=31536000, immutable # Content-Type: image/webp # ETag / Last-Modified # x-amz-request-id 或平台等价字段
对静态博客来说,构建产物应该是只读资产。部署流水线拥有写权限,运行时服务不需要写桶权限;如果确实要上传用户文件,应使用短期签名 URL 和独立桶。
缓存键不是 URL 的简单复制
CDN 判断是否命中时会看 Host、路径、查询字符串、Header 和 Cookie 策略。很多低命中率问题并不是节点离用户远,而是缓存键把无意义的查询参数或全部 Cookie 都包含了。
- 对 CSS、JS、图片、字体,忽略统计参数和会话 Cookie。
- 对带版本参数的资源,保留版本参数或直接使用指纹路径。
- 对 HTML,谨慎使用 Cookie,避免登录态导致页面无法公共缓存。
- Vary 头只保留必要项,例如压缩编码,不要对每个请求头变化。
? 后随机数用于本地调试,线上也保留这个参数,导致同一张图片在全球节点重复缓存。回源链路要少做无意义工作
CDN 未命中时会回源。回源走 HTTPS、保持连接复用、启用压缩和条件请求,可以减少延迟和流量费用。如果源站是对象存储,还应确认平台生成的权限头、编码头不会被误缓存。
对个人博客,一个简单的分层是:边缘节点缓存入口 HTML 数分钟,指纹资源一年,错误页不缓存过久。这样发布后用户能在可接受时间内看到新文章,同时大部分流量仍然由边缘承担。
安全头和访问控制
静态资源分发不只是速度问题。至少应配置这些响应头:HSTS、X-Content-Type-Options、Referrer-Policy、Content-Security-Policy。CSP 可以先从限制脚本来源、样式来源和框架嵌入开始,再逐步收紧。
# 基础安全头示例 Strict-Transport-Security: max-age=31536000; includeSubDomains X-Content-Type-Options: nosniff Referrer-Policy: strict-origin-when-cross-origin Content-Security-Policy: default-src 'self'; img-src 'self' data:; style-src 'self'; frame-ancestors 'none'
如果使用第三方统计或字体,需要把对应来源加入 CSP,并评估是否值得引入外部依赖。本站当前倾向不引入第三方脚本,减少请求、隐私和备案相关的不确定性。
发布与失效策略
- 构建时生成内容哈希文件名。
- 先上传指纹资源,再上传新的 HTML。
- 为 HTML 和站点地图发送缓存刷新或等待短 TTL 过期。
- 抽查首页、文章页、CSS、JS 和图片的响应头。
- 保留上一版构建产物,便于快速回滚。
缓存刷新是兜底手段,不应成为每次发布的常态依赖。若必须频繁刷新大量 URL,说明缓存键或版本策略设计有问题。
验证指标
- CDN 命中率:长期目标通常在 90% 以上,具体取决于动态内容比例。
- 回源流量:不应随访问量线性无限增长。
- LCP:主要页面在目标区域的表现稳定。
- 错误率:404、403 和 5xx 分开统计。
- 费用:出口流量、请求数、刷新次数一起看。
这套实践的价值在于,当用户分布扩大时,不需要重写应用,只需要调整区域和缓存策略。对象存储保存唯一可信版本,边缘负责分发,入口 HTML 控制更新节奏。