1. 前言与场景描述
在现代前后端分离的架构中,前端通常使用 Vue.js、React 等框架开发。开发完成后,通过构建工具(Webpack/Vite)打包成一堆静态文件(HTML, CSS, JS, Images)。
我们的目标是 :
将这些静态文件放到服务器上,利用 Nginx 作为 Web 服务器响应用户的 HTTP 请求。同时,Nginx 还需要充当"反向代理",将前端对 /api 的请求转发给后端的 Java/Go/Python 服务,从而解决跨域问题。
假设环境:
- 操作系统:CentOS 7 / Ubuntu 20.04 或 Docker 环境
- 前端项目:一个标准的 Vue 3 项目
- 后端地址 :
http://192.168.1.100:8080 - 域名 :
www.example.com
2. 准备工作:获取前端包
首先,你需要在本地开发环境中将代码打包。
bash
# 在项目根目录下执行
npm run build
# 或者
yarn build
执行完毕后,项目根目录下会生成一个 dist(或 build)文件夹。结构大致如下:
text
dist/
├── css/
├── js/
├── img/
├── favicon.ico
└── index.html <-- 入口文件
关键点 :你需要把这个 dist 文件夹完整地上传到服务器的某个目录,例如 /usr/share/nginx/html/my-app。
3. Nginx 基础安装与配置(Linux 宿主机模式)
如果直接在 Linux 服务器上运行 Nginx:
3.1 安装 Nginx
bash
# CentOS
sudo yum install epel-release
sudo yum install nginx
# Ubuntu
sudo apt update
sudo apt install nginx
# 启动并设置开机自启
sudo systemctl start nginx
sudo systemctl enable nginx
3.2 配置文件结构
Nginx 的主配置文件通常位于 /etc/nginx/nginx.conf。为了便于管理,我们通常不直接修改主文件,而是在 /etc/nginx/conf.d/ 目录下创建一个新的 .conf 文件,例如 my-app.conf。
3.3 编写最基础的配置
新建文件 /etc/nginx/conf.d/my-app.conf:
nginx
server {
# 监听端口,HTTP 默认为 80
listen 80;
# 服务器域名或 IP
server_name www.example.com;
# 日志路径(建议配置,方便排错)
access_log /var/log/nginx/my-app.access.log;
error_log /var/log/nginx/my-app.error.log;
# 核心配置:静态资源映射
location / {
# 指定静态资源文件的根目录
# 这里对应你上传 dist 文件夹的绝对路径
root /usr/share/nginx/html/my-app;
# 指定默认首页
index index.html index.htm;
}
}
测试并重载:
bash
nginx -t # 检查语法是否正确
nginx -s reload # 重载配置
此时,访问 http://www.example.com,你应该能看到你的页面了。但是,这还只是个开始,接下来的配置才是重头戏。
4. 进阶配置一:解决 SPA(单页应用)刷新 404 问题
问题描述 :
如果你的前端使用了 History 路由模式(例如 Vue Router 的 history mode),当你点击页面跳转到 /user/profile 时一切正常,但如果你在 /user/profile 页面点击浏览器的刷新按钮 ,或者直接复制这个 URL 打开,Nginx 会报 404 Not Found。
原因 :
Nginx 默认会去 root 目录下找名为 user 文件夹下的 profile 文件。但这是一个单页应用,实际上只有 index.html,不存在物理路径 /user/profile。
解决方案 :
利用 try_files 指令。
nginx
server {
listen 80;
server_name www.example.com;
root /usr/share/nginx/html/my-app;
index index.html;
location / {
# 核心指令 try_files
# 含义:尝试按照顺序访问文件
# 1. $uri: 找有没有对应的具体文件(比如 style.css)
# 2. $uri/: 找有没有对应的目录
# 3. /index.html: 如果前两个都找不到,无条件重定向到 index.html
try_files $uri $uri/ /index.html;
}
}
这样配置后,Nginx 发现找不到文件时,就会把请求交给 index.html,前端的 JS 路由接管 URL 并渲染正确的组件。
5. 进阶配置二:反向代理(解决跨域与 API 转发)
问题描述 :
前端代码中请求接口写的是 axios.get('/api/users')。
如果不配置代理,请求会发送到 http://www.example.com/api/users。但后端接口实际上在 http://192.168.1.100:8080/api/users。此外,如果前后端域名不同,还会产生 CORS 跨域问题。
解决方案 :
在 Nginx 中配置 proxy_pass。
nginx
server {
# ... 省略前面的配置 ...
# 匹配所有以 /api/ 开头的请求
location /api/ {
# 后端服务地址
# 注意:结尾是否有斜杠 '/' 区别很大,下面详细说明
proxy_pass http://192.168.1.100:8080/;
# 代理设置(标准配置,直接复制即可)
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;
}
}
关于 proxy_pass 结尾斜杠的"天坑"说明:
- 带斜杠 :
proxy_pass http://ip:port/;- 请求
http://domain/api/user-> 转发到http://ip:port/user - Nginx 会把
/api/切掉。
- 请求
- 不带斜杠 :
proxy_pass http://ip:port;- 请求
http://domain/api/user-> 转发到http://ip:port/api/user - Nginx 会把完整的路径拼接到后端地址后。
- 请求
通常后端接口本身包含 /api 前缀时,使用不带斜杠的方式;如果后端不包含前缀,使用带斜杠的方式剔除前缀。
6. 进阶配置三:性能优化(Gzip 与 缓存)
前端构建出的 app.js 和 chunk-vendors.js 往往比较大,必须开启 Gzip 压缩,并设置强缓存。
nginx
server {
# ... 基础配置 ...
# --- 开启 Gzip 压缩 ---
gzip on;
# 启用 gzip 压缩的最小文件,小于设置值的文件将不会压缩
gzip_min_length 1k;
# gzip 压缩级别,1-9,数字越大压缩的越好,也越占用CPU时间
gzip_comp_level 6;
# 进行压缩的文件类型
gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
# 是否在http header中添加Vary: Accept-Encoding,建议开启
gzip_vary on;
# --- 浏览器缓存策略 ---
# 1. 针对 index.html:永远不缓存
# 因为 index.html 引用了带 hash 的 js/css,如果它被缓存了,发版后用户将无法加载新资源
location = /index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
# 这里需要重新指定 root,或者利用外层的 root 配置
try_files $uri $uri/ =404;
}
# 2. 针对静态资源(JS/CSS/图片):设置超长缓存
# Webpack/Vite 打包后的文件名都带 hash (e.g., app.a1b2c3d4.js)
# 文件内容变了 hash 才会变,所以可以放心设置永久缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y; # 缓存一年
add_header Cache-Control "public, no-transform";
access_log off; # 静态资源访问不记录日志,减少磁盘 I/O
}
# 其他配置...
}
7. 高级部署:Docker 化(推荐)
现在很少直接在物理机上装 Nginx 了,使用 Docker 部署更加干净、可移植。
我们将使用 Multi-stage builds (多阶段构建):在一个 Dockerfile 中完成"构建"和"部署"两步。
7.1 准备 Dockerfile
在项目根目录下(和 package.json 同级)创建名为 Dockerfile 的文件:
dockerfile
# --- 第一阶段:构建阶段 ---
# 使用 Node 镜像打包
FROM node:18-alpine as build-stage
# 设置工作目录
WORKDIR /app
# 先复制 package.json 安装依赖(利用 Docker 缓存层)
COPY package*.json ./
RUN npm install --registry=https://registry.npmmirror.com
# 复制源代码并构建
COPY . .
RUN npm run build
# --- 第二阶段:生产环境 Nginx ---
FROM nginx:stable-alpine as production-stage
# 复制第一阶段构建好的 dist 目录到 Nginx 默认目录
# 注意:Vite 默认打包目录是 dist,如果是 create-react-app 可能是 build
COPY --from=build-stage /app/dist /usr/share/nginx/html
# 复制自定义的 Nginx 配置文件(见下文 7.2)
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 暴露端口
EXPOSE 80
# 启动 Nginx,daemon off 保证容器不退出
CMD ["nginx", "-g", "daemon off;"]
7.2 准备 nginx.conf
在项目根目录下创建一个 nginx.conf 文件(这个文件会被 copy 进镜像):
nginx
server {
listen 80;
server_name localhost; # Docker 内部通常用 localhost 即可
# 开启 gzip
gzip on;
gzip_min_length 1k;
gzip_comp_level 6;
gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
# 解决 Router History 模式 404
try_files $uri $uri/ /index.html;
}
# 接口代理
location /api/ {
# 注意:在 Docker 中,如果后端也在 Docker 里,这里写后端容器的服务名
# 如果后端在宿主机,可以使用 host.docker.internal (Mac/Win) 或 宿主机真实 IP
proxy_pass http://backend-service:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
7.3 构建与运行
bash
# 1. 构建镜像
docker build -t my-frontend-app:v1 .
# 2. 运行容器
# -d 后台运行
# -p 80:80 将宿主机的 80 端口映射到容器的 80 端口
docker run -d -p 80:80 --name my-frontend my-frontend-app:v1
8. 完整 Nginx 配置文件模板 (Summary)
为了方便你直接复制使用,这里提供一个整合了上述所有特性的完整配置文件:
nginx
# /etc/nginx/conf.d/frontend.conf
server {
# 1. 端口与域名
listen 80;
server_name www.example.com;
# 2. 根目录设置
root /usr/share/nginx/html/dist;
index index.html;
# 3. 日志设置
access_log /var/log/nginx/host.access.log main;
error_log /var/log/nginx/host.error.log warn;
# 4. Gzip 压缩优化
gzip on;
gzip_static on; # 如果存在 .gz 文件直接使用,不现场压缩
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_comp_level 6;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";
# 5. 主应用路由 (SPA 支持)
location / {
# 解决单页应用 History 模式刷新 404 问题
try_files $uri $uri/ /index.html;
# 针对 index.html 设置协商缓存或不缓存
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# 6. 静态资源长缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
access_log off;
}
# 7. 反向代理接口
location /api/ {
# 假设后端接口地址
proxy_pass http://127.0.0.1:8080/api/;
# 传递真实 IP
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 支持 WebSocket (如果需要)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# 8. 错误页面处理
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
9. 常见报错排查 (Troubleshooting)
Q1: 访问页面出现 403 Forbidden
原因 :Nginx 启动用户(通常是 nginx 或 nobody)没有权限读取你的 dist 目录。
解决:
- 修改
nginx.conf头部,将user nginx;改为user root;(简单粗暴,但不安全)。 - 或者赋予目录权限:
chmod -R 755 /usr/share/nginx/html/dist。
Q2: 访问页面白屏,控制台报错 Uncaught SyntaxError: Unexpected token <
原因 :这通常是因为 Nginx 没找到 JS/CSS 文件,触发了 try_files 回退到了 index.html。浏览器把 index.html 的 HTML 内容当成了 JS 解析,所以报错。
排查:
- 检查
build时的publicPath或base设置。如果项目部署在子路径(如/app/),前端构建配置必须设置对应的 base。 - 检查 Nginx
root路径是否正确。
Q3: 反向代理接口报 502 Bad Gateway
原因 :Nginx 连不上后端服务。
排查:
- 后端服务没启动。
- 防火墙拦截了端口。
- 如果用了 Docker,检查容器网络(Docker 容器内无法通过 127.0.0.1 访问宿主机服务)。
10. 总结
Nginx 挂载前端包的核心步骤可以概括为:
- Build :生成
dist静态资源。 - Root:配置 Nginx 指向该目录。
- Try_files:配置兜底策略解决 SPA 路由问题。
- Proxy:配置反向代理打通后端 API。
掌握了这份指南,无论是简单的个人博客还是复杂的企业级中台系统,你都能从容应对其前端部署工作。希望这份翔实的教程对你有所帮助!