可行,但需确保缓存响应未压缩、非分块、且 mime 类型在 sub_filter_types 中;关闭 gzip、启用 proxy_buffering、精准匹配路径、开启全局替换并忽略失效头。

直接在 Nginx 网关层用 sub_filter 修正缓存响应中的 CDN 链接是可行的,但必须满足几个硬性前提——它不修改已压缩内容、不处理分块响应、也不作用于缓存未命中的原始流。关键在于:让缓存响应本身是“可被 sub_filter 处理”的格式。
确保响应体未压缩且非 chunked
sub_filter 只能处理明文响应体。如果后端返回了 gzip 或 brotli 压缩内容,或使用了 Transfer-Encoding: chunked,替换会完全失效。
- 在对应
location块中显式关闭压缩:gzip off;(推荐)或gzip_disable "msie6";(仅禁用特定 UA) - 确认上游服务未设置
Transfer-Encoding: chunked;若无法控制,可在 Nginx 中加proxy_buffering on;+ 合理的proxy_buffer_size和proxy_buffers,强制收全响应再处理 - 避免与
proxy_cache混用时出现“缓存命中但未解压”的情况:Nginx 默认不会对压缩内容做 sub_filter,所以缓存策略应基于未压缩响应建立(例如 upstream 返回时就禁用 gzip)
精准匹配 CDN 路径并安全替换
sub_filter 是字面量、大小写敏感的流式替换,不解析 HTML 结构。路径边界和空格必须严格一致,否则易漏替或错替。
- 用完整路径前缀匹配,避免歧义:比如替换
'/static/js/app.js'而不是'/static/',后者可能误改/static-map.json或注释里的字符串 - 若需批量替换 CDN 域名,建议统一构建时使用占位符(如
__CDN_HOST__),再用 sub_filter 替换该占位符,比匹配真实域名更稳定 - 注意 URL 编码与引号包裹:HTML 中路径常出现在双引号内(
src="/static/a.js"),替换时保留引号结构,例如:sub_filter 'src="/static/' 'src="https://cdn.example.com/static/';
适配缓存响应的 MIME 类型
默认 sub_filter 只处理 text/html,但静态资源链接常出现在 JS、CSS、甚至 JSON 接口里(如前端配置文件)。
- 显式扩展支持类型:
sub_filter_types text/css application/javascript application/json; - 若缓存中包含内联脚本或样式(如 Vue SSR 输出的
<script>window.__INIT__=...</script>),确保这些响应也落在sub_filter_types范围内 - 不要依赖
application/octet-stream或二进制类型——sub_filter 对它们无效
启用全局替换并保留响应头一致性
缓存响应通常是完整、一次返回的,适合开启全量替换,但要注意响应头是否因内容变更而失效。
- 设
sub_filter_once off;,确保所有匹配位置都被替换(如多个src或href) - 加上
sub_filter_last_modified off;,防止替换后响应体变化但Last-Modified头未更新,导致浏览器缓存校验失败 - 若原始响应含
ETag,替换后该值已失效;建议配合proxy_ignore_headers ETag;或移除ETag,避免条件请求异常











