在单服务器环境下实现前后端共存,核心思路其实就一句话:前端静态资源由 Nginx(或 Apache)直接提供,后端 API 请求通过反向XX转发给 Node.js/Python/Java 等运行环境。
不要搞什么“前端打包后塞进后端项目目录让后端渲染”这种老派做法,除非你用的是 PHP 这种天生全栈的语言。现代 Web 开发(Vue/React/Angular + Spring Boot/Go/Node),最标准、性能最好、维护最清晰的方案就是 Nginx 做反向XX。
下面我以国内云服务器(阿里云 ECS、腾讯云 CVM 等)最常见的 Linux 环境(CentOS 7/8 或 Ubuntu 20.04+)为例,讲清楚怎么配。假设你的后端跑在 http://localhost:3000,前端静态文件在 /var/www/html。
一、 架构逻辑图解
用户浏览器
↓ (请求 /api/xxx)
Nginx (监听 80/443)
├── 匹配到静态文件 (.js, .css, .html, img...) → 直接从磁盘读取返回
└── 匹配到 /api/* → proxy_pass 转发到 localhost:3000 (你的后端服务)
这样做的好处:
- 动静分离:Nginx 处理静态文件的速度是毫秒级的,不占用后端 CPU。
- 跨域解决:同源策略下,前端和后端如果在不同端口,会有 CORS 问题。通过 Nginx 统一暴露 80 端口,前端看到的都是同一个域名,天然解决跨域。
- 安全性:后端服务不需要直接暴露在公网,只监听本地回环地址(127.0.0.1)。
二、 具体配置步骤(以 Nginx 为例)
1. 准备环境
确保你已经安装了 Nginx,并且你的后端服务已经启动并监听在 127.0.0.1:3000(或其他端口)。
2. 放置前端代码
将前端构建好的 dist 或 build 文件夹内容,上传到服务器的一个目录,比如 /usr/share/nginx/html 或者你自己定义的 /var/www/myapp/frontend。
注意:如果你用的是 Vue/React,记得在构建时设置好
publicPath或base,通常默认是/即可。如果部署在非根路径,需要调整。
3. 编写 Nginx 配置文件
找到你的 Nginx 配置文件,通常在 /etc/nginx/nginx.conf 或者 /etc/nginx/conf.d/default.conf。建议新建一个文件如 /etc/nginx/conf.d/app.conf,然后在主配置中 include 它。
以下是生产环境推荐的完整配置模板:
server {
listen 80;
server_name your_domain.com; # 换成你的域名或 IP
# 前端静态资源目录
root /var/www/myapp/frontend;
index index.html;
# 1. 处理 SPA 路由(Vue/React History 模式必备)
# 如果访问的路径不是物理文件,就返回 index.html,让前端 JS 去处理路由
location / {
try_files $uri $uri/ /index.html;
# 可选:开启 gzip 压缩,提升加载速度
gzip on;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/json image/png image/jpeg;
}
# 2. 后端 API 反向XX
# 假设后端接口前缀是 /api
location /api/ {
# 转发到后端服务
proxy_pass http://127.0.0.1:3000/;
# 注意:proxy_pass 后面的 URL 末尾是否有斜杠很重要!
# 如果有斜杠,Nginx 会去掉 /api/ 这部分再转发,后端收到的是 /users
# 如果没有斜杠,Nginx 会把 /api/users 完整转发给后端
# 传递真实的客户端信息给后端
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_connect_timeout 60s;
proxy_read_timeout 60s;
}
# 3. 其他静态资源缓存优化(可选)
location ~* .(jpg|jpeg|png|gif|ico|css|js)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}
4. 关键细节解析
-
try_files $uri $uri/ /index.html;
这是单页应用(SPA)的标配。当你刷新/user/profile这个页面时,Nginx 会在磁盘里找有没有叫profile的文件,找不到就会 fallback 到index.html。这样前端 Router 才能接管路由,否则你会看到 404 Not Found。 -
proxy_pass后的斜杠陷阱location /api/+proxy_pass http://backend/;
请求/api/users/list-> 后端收到/users/listlocation /api/+proxy_pass http://backend;
请求/api/users/list-> 后端收到/api/users/list
根据你的后端代码怎么写来决定。通常为了干净,后端希望去掉
/api前缀,所以推荐带斜杠写法,或者在后端代码里用中间件剥离前缀。 -
WebSocket 支持(如果需要)
如果你的前后端通信用了 WebSocket,需要在location /ws/或全局添加以下配置:proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
三、 常见坑与排查
-
502 Bad Gateway
- 原因:Nginx 能连上,但后端挂了或者没启动。
- 检查:
curl http://127.0.0.1:3000/api/test看后端是否响应。 - 检查:防火墙是否放行了 3000 端口?(其实只要监听 127.0.0.1,防火墙不需要放行 3000,只需要放行 80/443)。
-
前端刷新 404
- 原因:忘记加
try_files,或者root路径写错了。 - 检查:
ls -l /var/www/myapp/frontend/index.html确保文件存在且 Nginx 用户有读权限。
- 原因:忘记加
-
CORS 错误还在?
- 原因:你可能把 API 请求写成了直接发往
localhost:3000而不是走 Nginx 的/api路径。 - 解决:前端 Axios/Fetch 的请求 URL 必须改为相对路径
/api/xxx或绝对路径http://yourdomain.com/api/xxx。
- 原因:你可能把 API 请求写成了直接发往
-
HTTPS 配置
- 现在没有 HTTPS 的网站会被浏览器标记为“不安全”。
- 推荐使用 Let’s Encrypt 免费证书(certbot),或者云厂商提供的 SSL 证书管理功能。
- 配置 HTTPS 只需在 Nginx 中添加
listen 443 ssl;并指定证书路径,然后将 HTTP 重定向到 HTTPS 即可。
四、 进阶:如果用 Docker 呢?
现在很多团队喜欢用 Docker Compose 部署。原理一样,只是容器化。
docker-compose.yml 示例:
version: '3'
services:
frontend:
image: nginx:alpine
volumes:
- ./frontend/dist:/usr/share/nginx/html
- ./nginx/conf.d:/etc/nginx/conf.d
ports:
- "80:80"
depends_on:
- backend
backend:
build: ./backend
expose:
- "3000"
# 注意:这里不要映射 3000 到宿主机,只暴露给 docker 内部网络
然后 Nginx 容器里的 proxy_pass 指向 http://backend:3000(因为它们在同一个 Docker 网络里)。
总结
单服务器前后端共存,Nginx 反向XX是唯一正解。
- 前端:交给 Nginx 当静态服务器。
- 后端:隐藏在内网,通过 Nginx 透传请求。
- 配置重点:搞定 SPA 路由的
try_files和 API XX的proxy_pass路径匹配。
别整那些花里胡哨的框架内置服务器托管静态文件的玩法,Nginx 在处理并发和静态资源上的优势是原生进程无法比拟的。配好一次,稳如老狗。
云技术博客