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. 准备工作:获取前端包
首先,你需要在本地开发环境中将代码打包。
# 在项目根目录下执行 npm run build # 或者 yarn build
执行完毕后,项目根目录下会生成一个 dist(或 build)文件夹。结构大致如下:
dist/ ├── css/ ├── js/ ├── img/ ├── favicon.ico └── index.html <-- 入口文件
关键点:你需要把这个 dist 文件夹完整地上传到服务器的某个目录,例如 /usr/share/nginx/html/my-app。
3. nginx 基础安装与配置(linux 宿主机模式)
如果直接在 linux 服务器上运行 nginx:
3.1 安装 nginx
# 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:
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;
}
}
测试并重载:
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 指令。
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。
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 压缩,并设置强缓存。
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 的文件:
# --- 第一阶段:构建阶段 --- # 使用 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 进镜像):
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 构建与运行
# 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)
为了方便你直接复制使用,这里提供一个整合了上述所有特性的完整配置文件:
# /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。
掌握了这份指南,无论是简单的个人博客还是复杂的企业级中台系统,你都能从容应对其前端部署工作。希望这份翔实的教程对你有所帮助!
到此这篇关于nginx部署前端项目实战指南及常见错误的文章就介绍到这了,更多相关nginx部署前端项目内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论