Skip to content

为什么配了 try_files 后,静态资源和 JS 还能正常拿到

更新: 6/28/2026 字数: 0 字 时长: 0 分钟

try_files $uri $uri/ /index.html 这种 SPA 兜底规则后,很多人担心所有请求都被重写到 index.html,导致 JS、CSS、图片拿不到。实际不会。原因在于 try_files按顺序逐个尝试,命中即停——只要磁盘上真有这个文件,它在第一步就被返回了,根本走不到最后的兜底。下面拆开讲。

一、try_files 的执行顺序规则

Nginx try_files 执行顺序

try_files 的语法是:

nginx
try_files file1 file2 ... uri_or_=code;

它的核心规则是:从左到右依次检查每个参数对应的文件/目录在磁盘上是否存在,遇到第一个存在的就立即用它响应并停止后续检查;前面全部不存在时,才使用最后一个参数作为兜底。

以最典型的前端配置为例:

nginx
location / {
    root /usr/share/nginx/html;
    try_files $uri $uri/ /index.html;
}

它的尝试顺序是:

  1. $uri:把请求路径拼到 root 后面,检查这个文件是否存在。比如请求 /static/app.js,就检查 /usr/share/nginx/html/static/app.js 在不在。
  2. $uri/:如果上一步的文件不存在,检查它作为目录是否存在(目录存在则按 index 指令找首页)。
  3. /index.html:前两步都没命中时,才回退到这个兜底文件,把 SPA 的入口页面返回给浏览器,由前端路由接管。

关键点有两个:

  • 最后一个参数是"兜底",不是"总是执行"。它只在前面所有真实文件都查不到时才生效。
  • 顺序不可颠倒。真实存在的文件优先级最高,永远排在兜底之前被命中。

二、静态资源 / JS 为什么能被正常命中

静态资源与前端路由的请求分流

理解了顺序规则,结论就很自然了:JS、CSS、图片这些资源是真实存在于磁盘上的文件,它们在 try_files 的第一步 $uri 就被命中并返回了,根本到不了 /index.html 这一步。

具体分两种请求看:

1)静态资源请求(真实文件存在)

浏览器请求 /static/js/app.4f2a.js。Nginx 执行 try_files

  • 第一步检查 $uri/usr/share/nginx/html/static/js/app.4f2a.js,文件存在 → 直接返回这个 JS 文件,附带正确的 Content-Type: application/javascript,流程结束。

兜底的 /index.html 压根没机会执行。CSS、图片、字体同理——只要构建产物确实放在了 root 目录下对应路径,第一步必然命中。

2)前端路由请求(无对应真实文件)

浏览器请求 /user/profile(这是 SPA 的前端路由,磁盘上并没有这么个文件或目录)。Nginx 执行 try_files

  • 第一步 $uri/user/profile 文件不存在;
  • 第二步 $uri//user/profile/ 目录也不存在;
  • 第三步兜底 → 返回 /index.html,浏览器加载到 SPA 入口,再由前端路由(如 React Router、Vue Router)根据地址栏渲染对应页面。

底层逻辑一句话概括try_files 用"磁盘上文件是否真实存在"作为分流开关。存在的(静态资源)走第一步原样返回,不存在的(前端虚拟路由)才走兜底。两类请求天然被区分开,互不干扰。

三、需要留意的两个细节

虽然机制本身没问题,但实践中有两点容易踩坑,值得提一句:

  • root / alias 路径要配对try_files$uri 是相对于 location 里的 root(或 alias)去磁盘上找文件的。如果 root 指错了目录,第一步查不到真实文件,JS 就会被错误地回退成 index.html——表现为"请求 JS 却返回了一段 HTML",浏览器报 Unexpected token '<'。遇到这种报错,先查 root 路径对不对。

  • 可以为静态资源单独开 location 提升效率。很多团队会把静态资源单独匹配出来,跳过兜底逻辑、顺便加上缓存头:

nginx
# 静态资源:长缓存,不走 SPA 兜底
location /static/ {
    root /usr/share/nginx/html;
    expires 1y;
    add_header Cache-Control "public, immutable";
}

# 其余路径:SPA 兜底
location / {
    root /usr/share/nginx/html;
    try_files $uri $uri/ /index.html;
}

这样静态资源走专属 location,前端路由走兜底 location,职责更清晰,缓存策略也好控制。