OpenResty 静态站:缓存策略与安全响应头落地
静态博客构建一次、到处分发,表面上很省事;真正上线后,瓶颈往往不在「能不能打开」,而在「打开得快不快、缓存对不对、头信息安不安全」。本文以 OpenResty(兼容 Nginx 配置)托管 Astro/Firefly 产物为例,给出一套可直接落地的缓存与安全响应头方案。
目标拆解
对静态站通常要同时满足:
- HTML 可快速更新:发文后用户刷新即见新内容,不靠硬刷新清缓存。
- 静态资源可长期缓存:
_astro/、字体、图片等带 hash 的文件,浏览器尽量本地复用。 - 安全基线:补齐常见响应头,降低点击劫持、MIME 嗅探、协议降级等风险。
- 传输更省:开启压缩,对文本资源减体积。
不要把「整站 expires 30d」一刀切——那是踩坑最快的方式。
目录与路由习惯
Firefly/Astro 构建后常见结构:
dist/ index.html posts/<slug>/index.html _astro/*.js|*.css # 文件名含内容 hash assets/ pagefind/对应 OpenResty root 指向发布目录(例如 /opt/openresty/www/blog),文章 URL 形如 /posts/<slug>/。
推荐 location 分层
核心思路:按扩展名与路径分流,而不是对 location / 统一缓存。
1. HTML:短缓存或禁用强缓存
# HTML:允许协商缓存,但禁止长期强缓存location ~* \.html$ { add_header Cache-Control "public, max-age=0, must-revalidate" always; add_header X-Content-Type-Options "nosniff" always; try_files $uri $uri/ $uri/index.html =404;}
# 目录型页面(Astro 的 /posts/xxx/)location / { add_header Cache-Control "public, max-age=0, must-revalidate" always; try_files $uri $uri/ $uri/index.html =404;}max-age=0, must-revalidate 的含义是:浏览器每次都要向源站确认是否过期;配合后续 ETag/Last-Modified,未改动时仍可 304,流量很小,但发文后能立刻生效。
若前面还有 CDN,可把 HTML 的 CDN 缓存设为 1–5 分钟,并在发布脚本里触发 purge;源站侧仍建议保持「可 revalidate」。
2. 带 hash 的构建产物:一年缓存 + immutable
location ^~ /_astro/ { access_log off; add_header Cache-Control "public, max-age=31536000, immutable" always; try_files $uri =404;}
location ^~ /assets/ { access_log off; add_header Cache-Control "public, max-age=31536000, immutable" always; try_files $uri =404;}Astro 打包进 _astro/ 的 JS/CSS 文件名含 hash,内容变了文件名就变,旧 URL 不会被错误复用,因此 immutable 是安全的。
3. 字体与图片
location ~* \.(woff2?|ttf|otf|eot)$ { access_log off; add_header Cache-Control "public, max-age=31536000, immutable" always; add_header Access-Control-Allow-Origin "*" always; # 若跨域用字体再开}
location ~* \.(png|jpe?g|gif|webp|avif|svg|ico)$ { access_log off; add_header Cache-Control "public, max-age=2592000" always; # 30 天,按需求调}没有 hash 的图片不要盲目 immutable,否则换图同名会「改不掉」。
安全响应头(站点级)
在 server { ... } 内统一加(对错误页也生效时用 always):
server { listen 443 ssl http2; server_name blog.example.com; root /opt/openresty/www/blog;
# 基础安全头 add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# 纯静态站可先用较宽松 CSP,再逐步收紧 # add_header Content-Security-Policy "default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self';" always;
# … ssl、location 等}说明:
- HSTS 只在确认全站 HTTPS、无混用 HTTP 子域后再开。
- CSP 对带内联脚本/第三方统计的主题容易误伤,建议先在报告模式或分阶段启用。
- OpenResty/Nginx 的
add_header在嵌套location里会「覆盖」上层,而不是合并——所以关键头要么只在最内层写全,要么用更现代的more_set_headers(headers-more 模块)统一管理。
压缩
gzip on;gzip_vary on;gzip_min_length 1024;gzip_comp_level 5;gzip_types text/plain text/css application/javascript application/json application/xml image/svg+xml;
# 若已编译 brotli 模块# brotli on;# brotli_comp_level 5;# brotli_types text/plain text/css application/javascript application/json application/xml image/svg+xml;HTML/CSS/JS/JSON/SVG 压缩收益明显;已压缩的 woff2、png、jpg、webp 不必再压。
发布后如何验证
发文或改配置后,用本地回环 + SNI 验证(端口按你的反代实际调整):
# 看 HTML 是否禁止长期强缓存curl -skI --resolve blog.example.com:443:127.0.0.1 \ https://blog.example.com/posts/some-slug/
# 看构建资源是否 immutablecurl -skI --resolve blog.example.com:443:127.0.0.1 \ https://blog.example.com/_astro/some-file.hash.js期望:
- 文章页:
HTTP/2 200,Cache-Control含max-age=0或短 max-age。 _astro资源:max-age=31536000且最好带immutable。- 安全头:至少能看到
X-Content-Type-Options、X-Frame-Options或你配置的等价头。
再配合一次真实浏览器:DevTools → Network,确认二次访问 HTML 走 304/协商,hash 资源走 disk cache。
和构建流水线的配合
静态站正确缓存的前提是 构建产物文件名稳定且内容寻址:
- 用主题/框架自带的 hash 资源命名(Astro 默认如此)。
- 发布采用「整目录替换」而不是零散覆盖,避免半新半旧。
- HTML 不长期强缓存;若用 CDN,发布脚本里加对应 URL 的 purge。
- 构建机 OOM 时优先限制并行、提高 swap 或
NODE_OPTIONS=--max-old-space-size=...,而不是关掉 pagefind/字体子集化后留下残缺产物。
示例发布节奏(概念上):
pnpm build → 清空 www → 拷贝 dist → (可选)CDN purge → curl 抽检首页与最新文章常见坑
| 现象 | 可能原因 | 处理 |
|---|---|---|
| 发文后仍看到旧文 | HTML 被 expires 30d 或 CDN 强缓存 | HTML 改为 revalidate;purge CDN |
| 改了 CSS 不生效 | 自定义 CSS 无 hash 且长缓存 | 文件名加版本号,或缩短 max-age |
| 安全头「配了但没有」 | 子 location 的 add_header 覆盖了父级 | 在返回 200 的那个 location 写全,或用 headers-more |
| 仅首字节快、整页慢 | 未开 HTTP/2、未压 JS、字体阻塞 | 开 http2、压缩、font-display: swap |
| 本地 curl 200、外网 404 | 反代 root/try_files 与发布目录不一致 | 对齐 root 与实际 dist 拷贝路径 |
最小可复制清单
root指向当前发布目录;try_files支持 Astro 的目录型 URL。/_astro/、带 hash 的 assets:max-age=31536000, immutable。- HTML 与页面路径:
max-age=0, must-revalidate(或 CDN 短 TTL + purge)。 - 站点级安全头 +(确认 HTTPS 后)HSTS。
- gzip/brotli 覆盖文本类型。
- 每次发布后用
curl -I抽检一篇新文章和一个_astro资源。
小结
OpenResty 托管静态博客,性能与正确性主要来自 分层缓存 和 发布可观测,而不是堆一堆模糊的 expires。HTML 求「新」,hash 资源求「稳」,安全头求「全站一致」。把这三件事写进配置和发布脚本,日更站点就能在「当天可见」和「重复访问足够快」之间取得平衡。
若你的主题还有 service worker 或 PWA,需要额外审查 precache 列表,避免把 HTML 永久钉在客户端——那是另一层「缓存」,比 Nginx 更难排查。纯静态 + 上述头策略,对个人技术博客通常已经够用。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!













