静态资源
这一课你将能做到
- 用
root、index、try_files 托管一个前端构建产物(SPA),并解释 URI 如何拼到磁盘路径
- 说明 CodeRoadMap 主站为何反代 Next、admin 为何 Nginx 静态,对照
prod.conf.example
- 为带 hash 的 js/css/图片写
expires,并区分「壳 HTML」与「不可变静态」的缓存策略
为什么要学
Nginx 发静态文件极快:零应用进程、内核 sendfile、少拷贝。管理后台、落地页、文档站常常 构建完直接丢给 Nginx。
CodeRoadMap 主站是 Next(SSR/RSC),不适合整站 root;但 admin.coderoadmap.cn 正是 root + try_files 的经典 SPA 托管。
把「静态托管」与「反代动态」分清,才不会把 Next 的 _next/static 误当成必须由 Nginx 读盘,或给 index.html 乱加一年缓存导致发版后用户卡旧壳。
先建立图像
请求 URI: /assets/app.abc123.js
root: /var/www/coderoadmap-admin/dist
磁盘文件: /var/www/coderoadmap-admin/dist + /assets/app.abc123.js
SPA 深链: /users/42
try_files: 文件?→ 目录?→ 回退 /index.html(前端路由接手)
| 站点 | 入口方式 | 原因 |
|---|
| www | proxy_pass → :3000 | SSR/RSC、动态路由、鉴权页 |
| admin | root + try_files | Vite 等静态构建,无 Node 常驻亦可 |
人话:静态像「自助取件柜」——柜子直接给你文件;动态反代像「柜台打电话到后厨」——后厨(Next/Java)现做。
跟着做
步骤 1:对照官方 root / index
阅读 Serving Static Content 中静态部分,记下:root 指定目录,URI 拼在后面;index 决定目录默认页。
步骤 2:读 prod 里 admin 块
打开 deploy/nginx/prod.conf.example 的 admin server:
server {
listen 443 ssl http2;
server_name admin.coderoadmap.cn;
# 构建产物目录(部署时把 dist 同步到此)
root /var/www/coderoadmap-admin/dist;
index index.html;
location /api/ {
# 管理端 API 仍进 Spring Boot
proxy_pass http://coderoadmap_backend/;
# … headers …
}
location / {
# SPA:真实文件优先,否则回退前端路由
try_files $uri $uri/ /index.html;
}
}
在笔记写两句:
- 静态资源与
/api/ 可共存于同一 server
页面路由不进 Java,接口才进 Java步骤 3:本机最小静态实验(可选)
sudo mkdir -p /var/www/lab-static
echo '<h1>lab</h1>' | sudo tee /var/www/lab-static/index.html
server {
listen 8088;
server_name localhost;
root /var/www/lab-static;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
nginx -t → reload → curl -i http://127.0.0.1:8088/。应看到 HTML。
再访问不存在路径,应 404(本实验用 =404;SPA 则改 /index.html)。
步骤 4:理解 try_files 对 SPA 的意义
前端路由 /settings/profile 在服务器上通常 没有同名文件。没有 try_files … /index.html 时 Nginx 返回 404;有则返回壳,由浏览器 JS 路由。
接口路径必须另写 location /api/,否则会被回退成 index.html(内容类型错误、前端误解析)。
步骤 5:gzip 与类型(http 级)
http {
gzip on;
gzip_vary on;
gzip_min_length 1024;
# 只压文本类;图片/woff2 往往已压缩
gzip_types text/plain text/css application/json
application/javascript text/xml application/xml;
}
应看到:压缩省带宽;CPU 换体积。过小的文件不必压(gzip_min_length)。
步骤 6:缓存头分层
# 带 content hash 的静态:可长缓存
location ~* \.(js|css|png|jpg|jpeg|gif|svg|woff2)$ {
expires 7d;
add_header Cache-Control "public, immutable";
}
# HTML 壳:避免用户卡在旧 index
location = /index.html {
add_header Cache-Control "no-cache";
}
主站若反代 Next,长缓存常打在 /_next/static/(见缓存课);本课先在 纯静态 admin 建立正确直觉。
讲清楚要点
- 因为
root 与 URI 拼接成磁盘路径,所以 root 应指向构建产物根(如 dist)——不要指到含源码的上层乱目录。
- 因为 SPA 深链没有真实文件,所以需要
try_files 回退 index.html——否则刷新子路由 404。
- 因为
/api/ 也是路径,所以静态站仍要单独 location 反代 API——避免 API 被当成前端路由。
- 因为主站依赖 Next 运行时,所以 www 用
proxy_pass 而不是整站 root——RSC/SSR 不是纯静态。
- 因为 hash 文件名可变,所以 js/css 可长缓存;HTML 应短缓存或 no-cache——发版体验取决于壳是否更新。
- 因为图片与字体多已压缩,所以 gzip 聚焦文本类型——避免重复压缩浪费 CPU。
- 因为官方静态模型简单明确,所以排错优先看:文件是否存在、权限、root 是否指对——再查 try_files 顺序。
你可能会懵的地方
-
root 和 alias 有什么差别?
root:location URI 保留并拼到 root 后。alias:用 alias 路径替换 location 前缀。入门先精通 root;用 alias 时务必注意尾斜杠配对。
-
Next 的 public/ 还要不要 Nginx root?
主站反代时,public/ 由 Next 自己提供。Nginx 不必再 mirror 一份,除非你做特殊边缘缓存。
-
try_files $uri $uri/ /index.html 最后一项为什么是 URI?
最后参数是 内部重定向的 URI(再走一遍 location),不是随意磁盘路径写法。细节见官方 try_files。
-
静态站还要 HTTPS 吗?
要。admin 同样在 443 终止 TLS;安全头往往更严(见 ngx-headers)。
错法对照
| 错法 | 问题 | 正解 |
|---|
主站整站 root 指到 Next 项目目录 | SSR 失效、源码暴露风险 | www 反代 :3000 |
| SPA 无 try_files | 刷新子路由 404 | try_files $uri $uri/ /index.html |
给所有响应 expires 365d | HTML 壳卡住旧版本 | hash 资源长缓存;HTML no-cache |
| API 与静态共用一个宽 location | /api 返回 index.html | 独立 location /api/ |
| root 指到仓库而非 dist | 404 或发错文件 | 部署同步构建产物目录 |
自测三问
- URI
/logo.png 在 root /var/www/app/dist 时对应哪个磁盘路径?
- 为何 admin 需要
try_files,而 /api/ 不能靠它回退?
- 为什么带 hash 的 js 可以
immutable,而 index.html 通常不行?
小结一句话
静态 = root + index + try_files;CodeRoadMap 主站动态反代,admin 静态托管并共用 /api 上游。
官方延伸