React + FastAPI 前后端分离架构下的 Nginx 配置实践
前言
在现代 Web 开发中,前后端分离已经成为主流架构。前端使用 React 等框架构建单页应用(SPA),打包成静态资源;后端使用 FastAPI 等框架提供 RESTful API。两者通过 HTTP 接口通信,各自独立开发、独立部署。
在这套架构中,Nginx 扮演着至关重要的角色:它既是前端静态资源的托管服务器,又是后端 API 的反向代理,同时还承担着 HTTPS 终结、压缩、缓存、安全防护等职责。本文将以 React + FastAPI 为例,系统讲解一份生产可用的 Nginx 配置,并重点剖析前端静态资源托管中容易被忽视的细节。
一、整体架构
我们假设一个典型的部署结构:
- React 前端 :执行
npm run build(或vite build)后,生成的静态文件位于/var/www/frontend/dist; - FastAPI 后端 :通过 Uvicorn 或 Gunicorn 运行在本地的
127.0.0.1:8000; - Nginx :作为对外唯一入口,监听 80/443 端口,将
/api/开头的请求转发给 FastAPI,其余请求交由 React 静态资源处理。
这种设计的好处在于前后端同域访问,天然规避了跨域问题,同时对外只暴露一个入口,安全性和可维护性都更好。
二、完整配置概览
先来看整体配置骨架,后文会逐块展开:
nginx
upstream fastapi_backend {
server 127.0.0.1:8000;
keepalive 32;
}
# HTTP 自动跳转 HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on;
server_name example.com;
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
root /var/www/frontend/dist;
index index.html;
client_max_body_size 20m;
# ...(静态资源、API 代理、SPA 回退等,见下文)
}
三、React 前端静态资源托管
前端部分是本文的重点。React 静态资源的托管看似只是「把文件发出去」,但缓存策略、SPA 路由、压缩、安全头这几处细节处理不当,就会导致白屏、更新不生效、加载失败难以排查等线上问题。
3.1 分层缓存策略
React 前端的缓存遵循一个核心原则:入口文件不缓存,带 hash 的资源永久缓存。
Vite/Webpack 打包时会给 JS/CSS 文件名加上内容 hash(如 index-a1b2c3d4.js)。文件内容一旦改变,hash 就会改变,文件名也随之改变。因此这些文件可以放心设置一年的 immutable 缓存------因为内容变了就是新文件名,浏览器必然会重新请求。
而 index.html 就像一张「地图」,记录着当前版本引用了哪些 hash 文件。它本身必须每次都校验,否则用户会拿着旧的 index.html 去请求早已不存在的旧 hash 文件,从而导致白屏。
不同资源的缓存策略可以归纳如下:
| 文件类型 | 缓存策略 | 原因 |
|---|---|---|
index.html |
不缓存 | 引用了带 hash 的资源,必须实时更新 |
/assets/*.[hash].js |
一年 + immutable | 内容变则文件名变,绝对安全 |
| 图片 / 字体等 | 30 天 | 更新不频繁,无 hash 保障时留余量 |
service-worker.js |
不缓存 | 缓存会导致 PWA 无法更新 |
对应的配置如下:
nginx
# index.html ------ 绝不缓存
location = /index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
expires -1;
}
# 带 hash 的构建产物 ------ 永久缓存
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# 其他静态资源 ------ 中期缓存
location ~* \.(?:js|css|png|jpg|jpeg|gif|ico|svg|webp|woff2?|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public";
access_log off;
try_files $uri =404;
}
这里 Cache-Control: public, immutable 中的 immutable 值得特别说明:它告诉浏览器这个资源在有效期内绝不会变。这样即使用户主动点击刷新,浏览器也不会发起协商请求(If-Modified-Since)去询问服务器,而是直接使用本地缓存,进一步减少了请求数量。
另外注意静态资源正则块中加了 try_files $uri =404。如果不加,请求一个不存在的静态文件时会走到后面的 SPA 回退逻辑,返回 index.html(HTTP 200),从而掩盖资源加载失败的真实问题。加上 =404 能让缺失资源正确返回 404,便于线上排查。
3.2 SPA 路由回退
这是前后端分离中最容易被忽视、也最容易出问题的一点:
nginx
location / {
try_files $uri $uri/ /index.html;
}
React Router 在使用 HTML5 History 模式时,像 /user/profile 这样的路径在浏览器地址栏是合法的,但一旦用户直接访问或刷新该页面,浏览器会向服务器请求这个「文件」,而它在磁盘上并不存在,于是返回 404。
try_files $uri $uri/ /index.html 的逻辑是:先查找真实文件,找不到就返回 index.html,把路由控制权交还给前端的 React Router 处理。
需要强调的是,这条规则必须放在所有 location 的最后 。因为 Nginx 的 location 匹配有优先级,精确匹配(=)和正则匹配都会优先于这个通用的 /。如果放在前面,可能会误拦截本该走静态资源或 API 的请求。
3.3 压缩
开启 Gzip 压缩能显著减小文本类资源(JS/CSS/JSON)的传输体积:
nginx
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_comp_level 6;
gzip_types text/plain text/css application/json
application/javascript image/svg+xml
application/wasm;
# 若已编译 ngx_brotli 模块,可进一步开启:
# brotli on;
# brotli_static on;
如果构建流程中已经生成了预压缩的 .br 文件,配合 brotli_static on 可以直接返回预压缩产物,压缩率比 Gzip 更高,且无需运行时消耗 CPU。
3.4 安全响应头
nginx
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
这里的 always 参数确保即使在返回 4xx/5xx 状态码时也带上这些安全头------不加 always 时,Nginx 默认只在 2xx/3xx 响应中添加头部。
需要特别注意 add_header 的「继承覆盖」特性:如果某个 location 块内部又写了自己的 add_header,它会覆盖掉 server 块中的所有 add_header,而不是叠加。因此若某个 location 需要额外的头部,往往需要把 server 级别的头部在该 location 内重新声明一遍。
四、FastAPI 后端代理(简述)
后端部分相对简单,Nginx 只需把 /api/ 前缀的请求转发给 FastAPI:
nginx
location /api/ {
proxy_pass http://fastapi_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
需要注意 proxy_pass http://fastapi_backend; 末尾不带斜杠 ,会保留 /api/ 前缀转发给后端,此时 FastAPI 的路由也应以 /api/ 开头。若希望剥离前缀,可在末尾加斜杠写成 proxy_pass http://fastapi_backend/;。
后端启动时只需信任 Nginx 传来的代理头即可正确识别 HTTPS 和真实客户端 IP:
bash
uvicorn main:app --host 127.0.0.1 --port 8000 --proxy-headers
由于前后端同域访问,通常无需在 FastAPI 中配置 CORS ;只有当前端与 API 使用不同域名时才需要引入 CORSMiddleware。
五、小结
一份看似简单的 Nginx 配置,背后藏着不少工程经验。回顾本文,React 前端托管有几个关键要点值得记牢:入口文件 index.html 绝不缓存、带 hash 的资源永久缓存,是保证「发版即生效」的基础;SPA 路由回退规则要放在最后,且警惕它掩盖资源 404 的副作用;安全头记得加 always 并注意 add_header 的覆盖特性。
而后端代理的核心,则是理解 proxy_pass 尾部斜杠对路径的影响,以及通过代理头把真实请求信息透传给 FastAPI。