nginx try_files + index 指令的两个坑(前端 SPA / 静态站点)

某 Astro 静态站点 vhost 在路径互换那次先后踩了两个 try_files 坑,值得记。

坑 1:try_files <static-html> =404 吞掉所有静态资源 MIME

最初的兜底:

location / {
    try_files /admin/index.html =404;
}

意图是”任何 URL 都返回 admin dashboard HTML”,/uses//projects/ 这种 public 子页就不会从 dist 漏出来。

问题try_files 跟 URI 无关——它永远返回 /admin/index.html(因为这是字面常量,不是变量)。/admin/index.html 是个 .html 文件,nginx 按 MIME map 返 Content-Type: text/html

于是 /_astro/authelia.CwygM0gi.css 这种被 Astro <link rel="stylesheet"> 引用的资产也被服成 text/html → 浏览器拒绝当 CSS 加载 → 整个页面无样式。

修法:把”必须按真实路径返”的资产路径显式 prefix-match,catchall 那条只兜不存在的路径:

location ^~ /_astro/ {
    try_files $uri =404;
    expires 30d;
}
location = /favicon.svg { try_files $uri =404; }
 
# 路由页面
location /authelia/ { try_files /authelia/index.html =404; }
location /guest/    { try_files /dashboard/index.html =404; }
 
# 真兜底
location / {
    try_files /admin/index.html =404;
}

^~ 告诉 nginx “这条 prefix 命中后不再 regex match”,优先级高于普通 prefix。

坑 2:index index.html + try_files $uri ... 让根目录漏出 dist 里的 public 主页

为了修坑 1 试过另一种写法:

index index.html;
location / {
    try_files $uri $uri/ /admin/index.html;
}

意图是”先试真实文件,找不到再回退到 admin”。

问题:Astro 自己 build 出 dist/index.html公开站主页,不是 admin。请求 / 进 location /

  1. try_files $uri → URI 是 /,展开成 root 目录
  2. nginx 看到目录会用 index index.html 指令查 dist/index.html,找到
  3. 返回公开站主页

admin 入口看上去变成了公开介绍页,而且 /uses//projects/ 这些 public 子页也通过 $uri/ 漏出来。

修法

  • index 指令(避免目录自动找 index.html)
  • 在 catchall location /只 fallback 到 admin,不试 $uri
  • 静态资源走 ^~ /_astro/ 这种显式 prefix 兜住

总结规则

场景写法
路由页面(SPA / 多页静态站)location /path/ { try_files /path/index.html =404; } 字面常量,不靠 $uri
静态资源(CSS/JS/字体/图标)location ^~ /assets/ { try_files $uri =404; }^~ + $uri 按真实路径返
任何要 fall back 到 SPA 入口的兜底location / { try_files /spa/index.html =404; } 不要 $uri $uri/,会被同 dist 里的别的页面截胡
index 指令慎用——会让目录请求自动找 index.html,跟 try_files 配合容易产生意料外的命中