静态资源重复下载主因是Nginx未正确返回Cache-Control或Expires响应头:需确认location匹配、避免header被覆盖、区分强缓存(需max-age)与协商缓存(仅ETag/Last-Modified不够),并配合文件名哈希确保更新生效。

静态资源重复下载,多数不是带宽或网络问题,而是浏览器没收到有效的缓存响应头,导致每次请求都当成“新资源”重新拉取。关键看 Nginx 是否在响应中正确输出了 Cache-Control、Expires 等头部,且值符合预期。
确认浏览器是否真的收到缓存头
打开开发者工具 → Network 标签页 → 刷新页面 → 找到某个 JS 或 CSS 文件 → 点击它 → 查看 Response Headers:
- 必须看到 Cache-Control(如
public, max-age=31536000)或 Expires 字段;仅靠Last-Modified或ETag不足以触发强缓存,只能支持协商缓存(仍需一次请求) - 若完全缺失
Cache-Control和Expires,说明 Nginx 没配缓存策略,或被 location 匹配逻辑绕过 - 若存在但值为
no-cache、no-store、max-age=0,要检查是否被上游服务覆盖(比如后端 Node/PHP 返回了这些头),Nginx 默认会透传,需用proxy_hide_header Cache-Control+add_header覆盖
检查 location 块是否真正生效
很多配置写了 expires 1y,但实际根本没命中——因为请求路径没匹配到该 location。
- 用
curl -I https://yoursite.com/static/app.js查看响应头,比浏览器更干净(无插件干扰) - 确认该资源 URL 是否落在你配置缓存的
location内。例如你写了location /assets/ { expires 1y; },但前端引用的是/static/app.js,那就完全不生效 - 注意
alias和root对路径拼接的影响:用alias /data/static/;时,location /static/匹配后会把/static/截掉,去查/data/static/app.js;而root /data;会拼成/data/static/app.js。路径错,文件找不到,Nginx 可能 fallback 到 proxy_pass,结果返回的是后端头,不是你写的缓存头
验证缓存头是否被覆盖或清空
Nginx 的 add_header 指令不会继承,且同级多个 add_header 会覆盖前一个(除非用数组语法,但原生不支持)。常见陷阱:
- 在
server块里写了add_header Cache-Control "public";,但在location里又写了add_header Cache-Control "no-cache";→ 后者生效,前者被丢弃 - 用了
proxy_pass代理静态文件(不推荐),但后端返回了Cache-Control: private,Nginx 默认透传。此时需加proxy_hide_header Cache-Control;,再用add_header显式设置 - 某些安全加固配置会全局清除头,例如
more_clear_headers 'Cache-Control';(需 ngx_headers_more 模块),容易误删
排除构建与部署环节干扰
有时你改了 Nginx 配置,但浏览器仍在重复下载旧文件,真实原因可能是:
- 前端构建没更新文件名(比如仍是
app.js而非app.a1b2c3.js),你设了 1 年缓存,但内容变了,用户却还在用本地旧缓存 → 此时应配合内容哈希 + 强缓存,而非依赖协商缓存 - CDN 或中间代理(如企业防火墙)覆盖了 Nginx 返回的缓存头,可对比直接访问 IP(绕过 CDN)的响应头来判断
- 本地开发服务器(如 Vite/webpack dev server)开了热更新,自动注入 HMR 脚本并禁用缓存,容易误以为生产环境也如此


















