静态站点部署:root、alias 与 SPA 回退

前端项目上线在 Nginx 侧只做两件事:把构建产物放到一个目录,并告诉 Nginx “请求这个路径时去读那个文件”。难点全在细节上:rootalias 拼接路径的规则不同,try_files 少写一个 /index.html 就会让单页应用刷新后 404,权限少一个 x 位就是 403。本章把这三处讲透。

部署全流程

# 1) 本地构建产物(产物目录通常是 dist/ 或 build/)
pnpm install --frozen-lockfile && pnpm build

# 2) 先传到临时目录,再原子替换,避免上传中途用户看到半成品
rsync -avz --delete dist/ prod:/srv/www/site-next/
ssh prod 'sudo rm -rf /srv/www/site-prev && sudo mv /srv/www/site /srv/www/site-prev \
  && sudo mv /srv/www/site-next /srv/www/site'

# 3) 权限:目录 755、文件 644
sudo find /srv/www/site -type d -exec chmod 755 {} \; -o -type f -exec chmod 644 {} \;

# 4) 出 403 时逐级查看权限,一眼看出哪一层少了 x 位
namei -l /srv/www/site/index.html

最小静态站点配置

# /etc/nginx/conf.d/static.example.com.conf
server {
    listen 443 ssl;
    server_name static.example.com;
    ssl_certificate     /etc/nginx/certs/example.com.pem;
    ssl_certificate_key /etc/nginx/certs/example.com.key;
    root  /srv/www/site;      # 站点根目录,只写目录,不带末尾斜杠
    index index.html;
    location / {
        try_files $uri $uri/ =404;
    }
}

try_files 三种典型用法

# 1) 纯静态站:按 文件 → 目录 → 404 依次尝试
location / { try_files $uri $uri/ =404; }

# 2) SPA 单页应用:找不到的路径统一交给 index.html,由前端路由接管
location / { try_files $uri $uri/ /index.html; }

# 3) 自定义 404 页面:先声明 error_page,再用 internal 阻止直接访问
error_page 404 /404.html;
location = /404.html { internal; root /srv/www/site; }

SPA 必须用第 2 种写法:/user/123 这类路由在服务器上没有对应文件,若不回退到 index.html,用户刷新页面就会看到 404。回退只应针对 HTML 路由,/api/ 要用单独的 location /api/ { proxy_pass ...; },别被 location / 吃掉。

root 与 alias 的区别

维度rootalias
路径拼接root 值 + 完整 URIalias 值替换掉 location 前缀
结尾斜杠无影响(但别写尾斜杠)规则不同,极易出错
常见用途整站根目录、SPA把某个 URL 前缀映射到另一个目录
try_files 配合可靠历史上有已知怪异行为,尽量避开
# root:实际路径 = /srv/www + 完整 URI
location /static/ { root /srv/www; }        # /static/a.png → /srv/www/static/a.png

# alias:location 前缀被替换掉,两种写法结果完全不同
location /static/ { alias /srv/www/assets/; }   # 正确 → /srv/www/assets/a.png
location /static/ { alias /srv/www/assets; }    # 错误 → /srv/www/assetsa.png

结尾斜杠的两条经验:alias 的值与 location 前缀要么都带 /,要么都不带;两者不一致时拼接结果就会多一层或少一层目录。root 则不要带尾斜杠,写成 /srv/www/site/ 会拼出双斜杠,和 try_files 组合时容易出现难查的路径问题。

location 匹配优先级

形式示例优先级说明
精确匹配location = /healthz最高命中即停,不再看其他规则
前缀 + ^~location ^~ /static/次高匹配后不再尝试正则
正则 ~ / ~*location ~* \.(js|css)$先于普通前缀,按配置顺序取第一个
默认location /最低兜底

第 17 章会展开完整优先级与嵌套规则,这里记住一条就够:精确匹配最优先,^~ 优先于正则,正则优先于普通前缀

缓存头与 gzip

# 带哈希指纹的资源可以长缓存;HTML 必须不缓存,否则发布不生效
location ~* \.(js|css|png|jpg|jpeg|gif|svg|woff2?|ico)$ {
    expires 30d;
    add_header Cache-Control "public, immutable";
}
location = /index.html { add_header Cache-Control "no-cache, must-revalidate"; }

gzip 在主配置 http 块里开启(见第 14 章),text/cssapplication/javascriptimage/svg+xml 都值得压;图片、字体、woff2 本身已压缩,不要再压,省不下多少带宽还多耗 CPU。

验证方法

curl -I https://static.example.com/                         # 首页状态码与 Content-Type
curl -I https://static.example.com/assets/app.abc123.js     # 静态资源缓存头
curl -s https://static.example.com/user/123 | head -5       # SPA 路由应返回 index.html
curl -I -H 'Accept-Encoding: gzip' https://static.example.com/assets/app.abc123.js | grep -i content-encoding
curl -I --resolve static.example.com:443:47.98.x.x https://static.example.com/   # 绕过 DNS/CDN 直测

看响应头要抓三样:HTTP/1.1 200Content-Type 正确(CSS 被当成 text/html 说明 mime.types 没加载)、静态资源带 Cache-Control

403 与 404 的排查顺序

现象优先检查常见原因
403 Forbiddennamei -l /path/to/file目录缺 x 位、index 文件不存在、user 与属主不匹配
403 且日志 Permission deniedgetenforcels -ZSELinux 上下文不对(CentOS 系常见)
404 Not Foundnginx -T 找生效的 rootroot 路径拼错、产物未上传、alias 斜杠拼错
200 但页面空白看静态资源请求状态码资源 404 或构建的 base/publicPath 配错

排查动作固定为五步:

curl -I https://static.example.com/                  # 1. 先看状态码与 Server
sudo tail -100 /var/log/nginx/error.log              # 2. 看具体原因(Permission denied / No such file)
namei -l /srv/www/site/index.html                    # 3. 逐级权限
nginx -T | grep -A8 'server_name static.example.com' # 4. 确认生效的 server 与 root
sudo getenforce && sudo ls -Z /srv/www/site | head   # 5. SELinux 检查与修复

常见坑

现象做法
aliaslocation 尾斜杠不一致404 或路径多一层目录两边都带 / 或都不带
SPA 没配回退刷新子路由变 404try_files $uri $uri/ /index.html
目录权限少 x403,且只有该目录下的文件失败目录 755、文件 644
HTML 也被长缓存发布后用户看到的还是旧页面HTML 用 no-cache,带指纹资源才长缓存
上传途中直接覆盖线上目录用户访问到半成品传临时目录再 mv 原子替换
资源路径前缀与 Nginx 不匹配页面 200 但 JS/CSS 全 404构建的 base 与部署路径保持一致

小结:静态站点的三件事是把产物按“临时目录 + 原子替换”部署、把权限设成目录 755 文件 644、把 try_files 写对——纯静态用 =404、SPA 用 /index.html 回退;root 是拼接、alias 是替换,尾斜杠在 alias 上必须与 location 前缀一致;发布后在服务器上用 curl -I 核对状态码与 Content-Type,403 先查权限与 SELinux,404 先查 root 拼接与回退规则。

笔记加载中…