主题
04 · 静态文件服务
生活类比:Nginx 当静态文件服务器,就像图书馆借书台——有人来要"《红楼梦》",管理员去书架上拿一本递出去。整个过程不需要写作(生成),只是搬运(读文件)。
这一章你会学到:
- 把一个 HTML/JS/CSS 项目(包括 React/Vue 打包产物)部署到 Nginx
- 解决 SPA 刷新 404 这个新人 100% 踩的坑
- 配置缓存策略 / 防盗链 / 目录索引
1. 最小可用配置(30 秒)
nginx
server {
listen 80;
server_name localhost;
root /var/www/html; # 网站根目录
index index.html;
location / {
try_files $uri $uri/ =404;
}
}放一个 index.html 到 /var/www/html/,浏览器访问 localhost → 看到页面。就这么简单。
2. root vs alias —— 新手最容易混的两个词
nginx
location /static/ {
root /var/www/site; # 实际文件路径 = /var/www/site/static/foo.js
}
location /static/ {
alias /var/www/cdn/; # 实际文件路径 = /var/www/cdn/foo.js
}口诀:
- root = "在这个目录下找原 URL 路径"(保留前缀)
- alias = "把这个 URL 前缀替换成这个目录"(截掉前缀)
举个生活例子(图书馆找书):
| 借书单写的 | root /shelfA/ 表示 | alias /shelfB/ 表示 |
|---|---|---|
| /static/红楼梦.txt | 去 /shelfA/static/红楼梦.txt 找 | 去 /shelfB/红楼梦.txt 找 |
3. ⭐⭐⭐ SPA history 模式 fallback(必懂)
问题描述
你部署了一个 React/Vue 项目,进首页 OK,点路由 /about OK,但用户在 /about 页按 F5 刷新就 404。为啥?
正常路由跳转(前端劫持):
浏览器 ──► JS 改 URL → React Router 渲染 /about 组件
刷新页面(真实 HTTP 请求):
浏览器 ──► GET /about ──► Nginx 在硬盘上找 /about 文件 → 没有 → 404解决方案
nginx
location / {
try_files $uri $uri/ /index.html;
}try_files 的语义:依次尝试以下路径,命中就返回;都没有,最后用 index.html:
$uri→/about(找文件)$uri/→/about/(找目录)/index.html→ 兜底(关键)
/index.html 内联了所有 JS Bundle,浏览器拿到 HTML → 解析 → JS 跑起来 → 前端路由接管,渲染 /about 组件 ✅
⚠️ 这是新人 100% 踩的坑。哪怕你只学一个配置,也要记住
try_files。
4. 缓存策略 —— 让用户"光速"加载
4.1 三层缓存的真相
浏览器 ──► CDN ──► Nginx ──► 文件系统
↑ ↑ ↑
都看 Cache-Control 决定要不要缓存4.2 黄金组合:HTML 不缓存 + 资源 1 年缓存
原理:构建工具(Vite / Webpack)输出的 JS/CSS 文件名带哈希(如 app.a3f9c1.js),内容变 = 哈希变 = URL 变。所以:
- HTML:每次都要拿最新的(不缓存)
- 带哈希的 JS/CSS/图片:永远不会变(强缓存 1 年)
nginx
# HTML:不缓存
location ~* \.html$ {
add_header Cache-Control "no-cache, must-revalidate";
}
# 带哈希的静态资源:强缓存 1 年
location ~* \.[a-f0-9]{8,}\.(js|css|png|jpg|webp|svg|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# 没哈希的图片:缓存 7 天
location ~* \.(jpg|png|gif|webp|svg)$ {
expires 7d;
add_header Cache-Control "public";
}| Header | 含义 |
|---|---|
Cache-Control: no-cache | 每次都要去服务器问一下(但可用 304) |
Cache-Control: no-store | 浏览器和 CDN 都不缓存 |
Cache-Control: public | 浏览器 + CDN 都能缓存 |
Cache-Control: private | 仅浏览器可缓存 |
Cache-Control: max-age=31536000 | 缓存 1 年 |
Cache-Control: immutable | 浏览器收到后,刷新都不再问服务器(最强) |
5. 目录浏览(autoindex)
nginx
location /downloads/ {
autoindex on; # 列目录
autoindex_exact_size off; # 文件大小用 KB/MB(off)还是字节(on)
autoindex_localtime on; # 时间用本地时区
}⚠️ 生产环境默认关掉——别人浏览到敏感文件就糟了。仅在共享文件场景开启。
6. 防盗链(防止图片被白嫖)
nginx
location ~* \.(jpg|png|gif|webp)$ {
valid_referers none blocked server_names *.example.com;
if ($invalid_referer) {
return 403;
# 或者返回一张"防盗链警告图"
# rewrite ^/ /warning.png break;
}
}valid_referers 三个特殊值:
none:直接访问(没 Referer 头)也算合法 — 这样浏览器直接打开图片不会 403blocked:被代理或防火墙改成空 Referer 的也算合法server_names:当前 server 的 server_name- 后面可以加自己的白名单域名
7. gzip 压缩 —— 让 1MB JS 变 200KB
详见第 9 章。最小开关:
nginx
gzip on;
gzip_types text/css application/javascript application/json;
gzip_min_length 1k;8. CORS 跨域 —— 静态资源被另一个域引用
nginx
location ~* \.(js|css|woff2|svg)$ {
add_header Access-Control-Allow-Origin "*";
expires 1y;
}注意:如果同时还要
add_header Cache-Control,两个 add_header 都得写在同一个 location 块里——add_header在子 location 里如果再加一个,会覆盖父级所有 add_header(这又是个新人坑)。
9. 实战:部署一个 Vite 打包的项目
9.1 项目结构
/var/www/myapp/
├─ index.html ← 入口
├─ favicon.ico
└─ assets/
├─ index-a3f9c1.js ← 带哈希的 bundle
├─ index-b2e8a4.css
└─ logo-c4f6d2.png9.2 完整 nginx 配置
nginx
server {
listen 80;
server_name app.example.com;
root /var/www/myapp;
index index.html;
# 1. SPA history fallback
location / {
try_files $uri $uri/ /index.html;
}
# 2. 带哈希的资源 → 强缓存
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# 3. 网站图标
location = /favicon.ico {
access_log off;
log_not_found off;
expires 30d;
}
# 4. HTML 不缓存
location = /index.html {
add_header Cache-Control "no-cache, must-revalidate";
}
# 5. 安全:禁止访问隐藏文件
location ~ /\. {
deny all;
}
}10. ⚠️ 静态文件服务 8 个高频坑
- 404 老是找不到文件 → 看 error.log,95% 是
root/alias路径不对,或 worker 进程没权限读 - 403 Forbidden → 文件夹权限:
chmod 755 /var/www、chmod 644 *.html - SPA 刷新 404 → 没
try_files ... /index.html - 改了文件浏览器还是旧的 → 强缓存太狠 + 文件名没哈希;HTML 必须
no-cache - add_header 没生效 → 子 location 里又写了一个
add_header,覆盖了父级 - gzip 没生效 → 没在
gzip_types里加你的 MIME 类型;或者 nginx 在 CDN 后面被剥了Accept-Encoding alias末尾忘加斜杠 → URL 拼接出/var/www/cdnstatic/这种鬼东西- 大文件上传 413 → 加
client_max_body_size 100M;
11. 章末面试题速览
详见
qa.md第 9-11 题。
- SPA history 模式刷新 404 怎么办? →
try_files $uri $uri/ /index.html; root和alias区别? → root 保留 URL 前缀拼路径;alias 截掉前缀。- 如何让静态资源强缓存 1 年? → 文件名带哈希 +
expires 1y; add_header Cache-Control "public, immutable";
12. 一句话总结
静态文件服务的核心就是三件事:
try_files解决 SPA 路由、文件名带 hash 配合immutable解决缓存、用add_header控好 CORS / Cache-Control。
下一章 → 05 · 反向代理:让 Nginx 当个合格的"接线员"。
🎬 可视化演示
下方 demo 让你一键模拟:"不写 try_files 时刷新 404" vs "写了 try_files 时刷新成功"——直观感受 SPA fallback 的工作原理。
🎬 可视化演示
演示加载缓慢或样式异常?点此在新标签页打开 ↗