Nginx 部署前端项目实战指南

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 结尾斜杠的"天坑"说明

  1. 带斜杠proxy_pass http://ip:port/;
    • 请求 http://domain/api/user -> 转发到 http://ip:port/user
    • Nginx 会把 /api/ 切掉。
  2. 不带斜杠proxy_pass http://ip:port;
    • 请求 http://domain/api/user -> 转发到 http://ip:port/api/user
    • Nginx 会把完整的路径拼接到后端地址后。

通常后端接口本身包含 /api 前缀时,使用不带斜杠的方式;如果后端不包含前缀,使用带斜杠的方式剔除前缀。


6. 进阶配置三:性能优化(Gzip 与 缓存)

前端构建出的 app.jschunk-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 启动用户(通常是 nginxnobody)没有权限读取你的 dist 目录。
解决

  1. 修改 nginx.conf 头部,将 user nginx; 改为 user root;(简单粗暴,但不安全)。
  2. 或者赋予目录权限:chmod -R 755 /usr/share/nginx/html/dist

Q2: 访问页面白屏,控制台报错 Uncaught SyntaxError: Unexpected token <

原因 :这通常是因为 Nginx 没找到 JS/CSS 文件,触发了 try_files 回退到了 index.html。浏览器把 index.html 的 HTML 内容当成了 JS 解析,所以报错。
排查

  1. 检查 build 时的 publicPathbase 设置。如果项目部署在子路径(如 /app/),前端构建配置必须设置对应的 base。
  2. 检查 Nginx root 路径是否正确。

Q3: 反向代理接口报 502 Bad Gateway

原因 :Nginx 连不上后端服务。
排查

  1. 后端服务没启动。
  2. 防火墙拦截了端口。
  3. 如果用了 Docker,检查容器网络(Docker 容器内无法通过 127.0.0.1 访问宿主机服务)。

10. 总结

Nginx 挂载前端包的核心步骤可以概括为:

  1. Build :生成 dist 静态资源。
  2. Root:配置 Nginx 指向该目录。
  3. Try_files:配置兜底策略解决 SPA 路由问题。
  4. Proxy:配置反向代理打通后端 API。

掌握了这份指南,无论是简单的个人博客还是复杂的企业级中台系统,你都能从容应对其前端部署工作。希望这份翔实的教程对你有所帮助!

相关推荐
李剑一2 小时前
uni-app实现网络离线定位
前端·trae
知南x2 小时前
【STM32MP157 视频监控项目】(2) 移植 Nginx
stm32·nginx·音视频
码界奇点2 小时前
基于Vue3与TypeScript的后台管理系统设计与实现
前端·javascript·typescript·vue·毕业设计·源代码管理
ashcn20012 小时前
水滴按钮解析
前端·javascript·css
攀登的牵牛花2 小时前
前端向架构突围系列 - 框架设计(五):契约继承原则
前端·架构
豆苗学前端3 小时前
你所不知道的前端知识,html篇(更新中)
前端·javascript·面试
一 乐3 小时前
绿色农产品销售|基于springboot + vue绿色农产品销售系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端·宠物
zzjyr3 小时前
Webpack 生命周期原理深度解析
前端
xiaohe06013 小时前
💘 霸道女总裁爱上前端开发的我
前端·游戏开发·trae