Skip to content

04 · 静态文件服务

生活类比:Nginx 当静态文件服务器,就像图书馆借书台——有人来要"《红楼梦》",管理员去书架上拿一本递出去。整个过程不需要写作(生成),只是搬运(读文件)

这一章你会学到:

  1. 把一个 HTML/JS/CSS 项目(包括 React/Vue 打包产物)部署到 Nginx
  2. 解决 SPA 刷新 404 这个新人 100% 踩的坑
  3. 配置缓存策略 / 防盗链 / 目录索引

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

  1. $uri/about(找文件)
  2. $uri//about/(找目录)
  3. /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 头)也算合法 — 这样浏览器直接打开图片不会 403
  • blocked:被代理或防火墙改成空 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.png

9.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 个高频坑

  1. 404 老是找不到文件 → 看 error.log,95% 是 root / alias 路径不对,或 worker 进程没权限读
  2. 403 Forbidden → 文件夹权限:chmod 755 /var/wwwchmod 644 *.html
  3. SPA 刷新 404 → 没 try_files ... /index.html
  4. 改了文件浏览器还是旧的 → 强缓存太狠 + 文件名没哈希;HTML 必须 no-cache
  5. add_header 没生效 → 子 location 里写了一个 add_header,覆盖了父级
  6. gzip 没生效 → 没在 gzip_types 里加你的 MIME 类型;或者 nginx 在 CDN 后面被剥了 Accept-Encoding
  7. alias 末尾忘加斜杠 → URL 拼接出 /var/www/cdnstatic/ 这种鬼东西
  8. 大文件上传 413 → 加 client_max_body_size 100M;

11. 章末面试题速览

详见 qa.md 第 9-11 题。

  1. SPA history 模式刷新 404 怎么办?try_files $uri $uri/ /index.html;
  2. rootalias 区别? → root 保留 URL 前缀拼路径;alias 截掉前缀。
  3. 如何让静态资源强缓存 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 的工作原理。

🎬 可视化演示

演示加载缓慢或样式异常?点此在新标签页打开 ↗