Nginx + Vue Router 基础:SPA History 部署、反向代理与请求分流
适用场景:Vue 3 / Vite / Vue Router 4 / Node.js / Koa / Nginx
示例域名:
www.test.com文档定位:偏实战,适合前端、Node.js 开发在服务器部署 SPA 项目时查阅
建议 CSDN 标签:
Nginx、Vue3、Vue Router、Vite、前端部署
前言
前端项目部署到服务器后,经常会遇到这些问题:
- Vue Router
history模式刷新页面后 404; /oa、/oa/到底有什么区别;root和alias经常分不清;- 明明改了 Nginx 配置,却始终不生效;
/api/代理到 Node.js 后接口路径不对;- 想让移动端访问 H5、PC 访问 PC 页面;
- 需要根据请求头、参数、User-Agent 做简单分流;
- 403、404、502 出现以后,不知道应该先查哪里。
本文不追求覆盖 Nginx 的全部能力,而是集中讲清楚 前端 / Node.js 开发最常用、最容易出错的一部分。
1. Nginx 的整体请求流程
先建立一个最重要的心智模型:
text
浏览器请求
│
▼
Nginx
│
▼
根据域名寻找 server
│
▼
根据 URI 寻找 location
│
├───────────────┬────────────────┬────────────────┐
▼ ▼ ▼ ▼
静态文件 反向代理 重定向 拒绝/错误
root / alias proxy_pass return deny / error_page
│ │
▼ ▼
index/try_files Node/Koa/Java 等后端
例如访问:
text
https://www.test.com/oa/user/list
可能经历:
text
www.test.com
↓
找到对应 server
↓
URI = /oa/user/list
↓
匹配 location /oa/
↓
尝试读取真实文件
↓
文件不存在
↓
try_files 回退 /oa/index.html
↓
Vue 启动
↓
Vue Router 接管 /user/list
可以把 Nginx 简单理解成:
根据域名、URL、请求头、设备、参数等信息,决定请求下一步去哪里的"请求分流器"。
2. Nginx 配置层级:http、server、location、include
一个常见的拆分结构:
text
/etc/nginx/nginx.conf
│
└── http
│
├── include server/www/*.conf
│
├── include server/api/*.conf
│
└── include server/admin/*.conf
│
└── server
│
└── include location/www/*.conf
│
├── location /oa/
├── location /crm/
└── location /api/
示例:
nginx
http {
include /etc/nginx/conf.d/server/www/*.conf;
}
被加载的 server:
nginx
server {
listen 443 ssl;
server_name www.test.com;
include /etc/nginx/conf.d/location/www/*.conf;
}
再继续加载:
nginx
location /oa/ {
}
location /crm/ {
}
location /api/ {
}
include 最重要的理解
include 不是注释,也不是"参考配置"。
它的含义是:
把指定文件里的配置真正加载到当前位置。
如果缺少:
nginx
include /etc/nginx/conf.d/server/www/*.conf;
那么即使 www.test.com 的 server 文件本身写得完全正确,也不会生效。
请求链路
text
nginx.conf
↓
include server/*.conf
↓
server_name www.test.com
↓
include location/*.conf
↓
location /oa/
3. server:根据域名分流
server 可以理解为一个网站入口。
nginx
server {
listen 443 ssl;
server_name www.test.com;
}
主要处理:
text
https://www.test.com/*
另外一个:
nginx
server {
listen 443 ssl;
server_name blog.test.com;
}
主要处理:
text
https://blog.test.com/*
因此:
| 层级 | 主要作用 |
|---|---|
http |
Nginx HTTP 全局配置 |
server |
根据域名、端口区分网站 |
location |
根据 URI 区分业务 |
include |
加载其他配置文件 |
4. location:根据 URL 分流
例如:
nginx
location /oa/ {
}
可以处理:
text
/oa/
/oa/login
/oa/user/list
/oa/system/user
接口:
nginx
location /api/ {
}
可以处理:
text
/api/login
/api/user
/api/order/list
因此可以记住:
text
server → 根据域名分流
location → 根据 URI 分流
5. /oa、/oa/ 和 = /oa 的区别
5.1 location /oa
nginx
location /oa {
}
这是前缀匹配。
以下 URI 都可能匹配:
text
/oa
/oa/
/oa/user
/oa123
/oabcdef
原因是它们都以 /oa 开头。
5.2 location /oa/
nginx
location /oa/ {
}
匹配:
text
/oa/
/oa/user
/oa/system/user
但不会匹配:
text
/oa
/oa123
5.3 location = /oa
nginx
location = /oa {
}
= 表示 精确匹配。
只匹配:
text
/oa
不会匹配:
text
/oa/
/oa/user
/oa123
5.4 SPA 推荐组合
nginx
location = /oa {
return 301 /oa/;
}
location /oa/ {
root /home/html/project;
index index.html;
try_files $uri $uri/ /oa/index.html;
}
请求过程:
text
/oa
↓
精确匹配 location = /oa
↓
301
↓
/oa/
↓
location /oa/
↓
进入 SPA
6. location 常见匹配方式与优先级
常见形式:
nginx
location = /oa {
}
精确匹配。
nginx
location /oa/ {
}
普通前缀匹配。
nginx
location ^~ /oa/ {
}
优先前缀匹配。
nginx
location ~ \.php$ {
}
正则匹配,区分大小写。
nginx
location ~* \.(js|css|png|jpg)$ {
}
正则匹配,不区分大小写。
简化优先级
text
① = 精确匹配
↓
② 寻找最长前缀 location
↓
③ 如果最长前缀带 ^~
↓
直接使用,不再检查正则
↓
④ 否则检查正则 ~ / ~*
↓
⑤ 第一个命中的正则生效
↓
⑥ 没有正则命中
↓
使用之前找到的最长前缀
示例
nginx
location ^~ /oa/ {
...
}
location ~* \.js$ {
...
}
请求:
text
/oa/static/app.js
因为 /oa/ 使用了 ^~,所以会优先进入 /oa/,不会继续被 .js 正则 location 抢走。
7. root 的工作原理
示例:
nginx
location /oa/ {
root /home/html/project;
}
访问:
text
/oa/index.html
实际文件:
text
/home/html/project/oa/index.html
可以记公式:
text
真实路径 = root + 完整 URI
也就是:
text
/home/html/project
+
/oa/index.html
=
/home/html/project/oa/index.html
如果目录结构:
text
/home/html/project/
├── oa/
│ ├── index.html
│ └── assets/
└── crm/
├── index.html
└── assets/
那么:
nginx
location /oa/ {
root /home/html/project;
}
location /crm/ {
root /home/html/project;
}
非常自然。
8. alias 的工作原理
例如:
nginx
location /download/ {
alias /data/project/files/;
}
用户访问:
text
/download/a.zip
可以理解成把:
text
/download/
替换为:
text
/data/project/files/
最终访问:
text
/data/project/files/a.zip
9. root 和 alias 对比
| 对比项 | root |
alias |
|---|---|---|
| 核心逻辑 | 根目录 + 完整 URI | 用实际目录替换 location 前缀 |
| URL 和磁盘目录一致 | 推荐 | 可以但没必要 |
| URL 和磁盘目录不同 | 较绕 | 推荐 |
| SPA 项目 | 常用 | 可用但更容易绕 |
| 理解难度 | 较低 | 较高 |
记忆口诀
text
URL 路径和磁盘目录结构一致
→ root
URL 路径和实际目录完全不同
→ alias
10. index 的作用
nginx
index index.html;
表示访问目录时优先寻找默认首页。
例如:
nginx
location /oa/ {
root /home/html/project;
index index.html;
}
访问:
text
/oa/
对应:
text
/home/html/project/oa/index.html
11. Vue Router Hash 模式
Vue Router:
ts
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
routes,
})
URL:
text
https://www.test.com/oa/#/user/list
关键是:
text
#/user/list
# 后面的内容不会作为 HTTP 请求路径发送给服务器。
Nginx 实际看到的主要是:
text
/oa/
因此 Hash 模式通常不容易出现"刷新子路由 404"。
Hash 模式流程
text
浏览器地址:
/oa/#/user/list
│
▼
Nginx 看到:
/oa/
│
▼
返回 index.html
│
▼
Vue Router 读取:
#/user/list
12. Vue Router History 模式
Vue Router:
ts
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory('/oa/'),
routes,
})
URL:
text
https://www.test.com/oa/
https://www.test.com/oa/login
https://www.test.com/oa/user/list
URL 中没有 #,更加自然。
但是 History 模式有一个关键区别:
浏览器刷新时,完整路径会真正请求 Nginx。
例如:
text
https://www.test.com/oa/user/list
Nginx 收到:
text
GET /oa/user/list
13. 为什么 History 模式刷新会 404
假设用户已经打开:
text
/oa/
然后 Vue 执行:
ts
router.push('/user/list')
Vue Router 会通过浏览器 History API 修改 URL:
text
/oa/user/list
这一步通常不会重新请求新的 HTML。
因此页面正常。
但是用户按 F5:
text
浏览器
↓
GET /oa/user/list
↓
Nginx
↓
寻找真实文件:
/home/html/project/oa/user/list
↓
不存在
↓
404
因为:
text
/user/list
本质上只是 Vue Router 的前端路由,并不是服务器真实目录。
14. try_files 如何解决 History 刷新 404
标准配置:
nginx
location /oa/ {
root /home/html/project;
index index.html;
try_files $uri $uri/ /oa/index.html;
}
核心:
nginx
try_files $uri $uri/ /oa/index.html;
可以理解为:
text
① 尝试当前 URI 对应文件
↓
② 尝试当前 URI 对应目录
↓
③ 都不存在
↓
④ 返回 /oa/index.html
访问:
text
/oa/user/list
Nginx 先查:
text
/home/html/project/oa/user/list
不存在。
最终回退:
text
/oa/index.html
对应真实文件:
text
/home/html/project/oa/index.html
然后:
text
Nginx 返回 index.html
↓
Vue 启动
↓
Vue Router 读取当前浏览器地址
↓
createWebHistory('/oa/')
↓
去掉 /oa/ base
↓
内部路由 = /user/list
↓
显示 UserList 页面
这就是 Vue History 模式部署最核心的一条链。
15. Vue Router Base 的作用
ts
createWebHistory('/oa/')
其中:
text
/oa/
叫 Router Base。
它表示:
当前 Vue 应用部署在网站的
/oa/路径下。
例如:
ts
router.push('/login')
浏览器实际 URL:
text
/oa/login
而不是:
text
/login
关系:
| 浏览器 URL | Vue Router 内部路由 |
|---|---|
/oa/ |
/ |
/oa/login |
/login |
/oa/user/list |
/user/list |
/oa/system/user |
/system/user |
因此 routes 中不要重复写 /oa:
ts
// 正确
{
path: '/login'
}
而不是:
ts
// 不推荐
{
path: '/oa/login'
}
16. Vite base 和 Vue Router base 的区别
这是 SPA 二级目录部署最容易混淆的两个配置。
Vite
ts
export default defineConfig({
base: '/oa/',
})
主要控制:
- JS 地址;
- CSS 地址;
- 图片地址;
- 字体;
- 静态资源基础路径。
打包后可能得到:
html
<script src="/oa/assets/index-xxx.js"></script>
<link href="/oa/assets/index-xxx.css" rel="stylesheet">
Vue Router
ts
createWebHistory('/oa/')
主要控制:
/login/user/list/system/user
这些前端页面路由的基础路径。
对照表
| 配置 | 主要职责 |
|---|---|
Vite base |
控制 JS、CSS、图片等静态资源 |
Router base |
控制 Vue Router 页面路由 |
Nginx root |
URL 映射到哪个磁盘目录 |
Nginx try_files |
前端路由找不到真实文件时回退 index.html |
推荐写法
如果 Vite 已经:
ts
base: '/oa/'
Router 可以:
ts
createWebHistory(import.meta.env.BASE_URL)
减少重复配置。
17. 一个域名部署多个 Vue SPA
服务器目录:
text
/home/html/project/
├── oa/
│ ├── index.html
│ └── assets/
│
└── crm/
├── index.html
└── assets/
Nginx:
nginx
location = /oa {
return 301 /oa/;
}
location /oa/ {
root /home/html/project;
index index.html;
try_files $uri $uri/ /oa/index.html;
}
location = /crm {
return 301 /crm/;
}
location /crm/ {
root /home/html/project;
index index.html;
try_files $uri $uri/ /crm/index.html;
}
OA:
ts
// vite.config.ts
base: '/oa/'
ts
createWebHistory('/oa/')
CRM:
ts
// vite.config.ts
base: '/crm/'
ts
createWebHistory('/crm/')
最终:
text
www.test.com
│
├── /oa/
│ └── OA Vue SPA
│
├── /crm/
│ └── CRM Vue SPA
│
└── /api/
└── 后端服务
18. proxy_pass 反向代理
示例:
nginx
location /api/ {
proxy_pass http://127.0.0.1:3000/;
}
浏览器访问:
text
https://www.test.com/api/user/list
Nginx 将请求转发给:
text
http://127.0.0.1:3000/user/list
这就是反向代理。
请求流程
text
浏览器
https://www.test.com/api/user/list
│
▼
Nginx
│
▼
proxy_pass http://127.0.0.1:3000/
│
▼
Node / Koa / Java 后端
浏览器只知道 www.test.com,并不需要直接访问后端端口。
19. proxy_pass 最后的 / 为什么重要
下面两种配置不同。
有 /
nginx
location /api/ {
proxy_pass http://127.0.0.1:3000/;
}
请求:
text
/api/user/list
后端通常收到:
text
/user/list
/api/ 被替换掉。
没有 /
nginx
location /api/ {
proxy_pass http://127.0.0.1:3000;
}
请求:
text
/api/user/list
后端通常收到:
text
/api/user/list
对照
| 配置 | 浏览器请求 | 后端路径 |
|---|---|---|
proxy_pass http://127.0.0.1:3000/; |
/api/user |
/user |
proxy_pass http://127.0.0.1:3000; |
/api/user |
/api/user |
接口代理后莫名 404 时,这里是重点检查对象。
20. 常用代理请求头
推荐模板:
nginx
location /api/ {
proxy_pass http://127.0.0.1:3000/;
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;
}
| Header | 作用 |
|---|---|
Host |
用户访问的域名 |
X-Real-IP |
客户端 IP |
X-Forwarded-For |
代理链 IP |
X-Forwarded-Proto |
http / https |
21. WebSocket 代理
常见配置:
nginx
location /ws/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
核心:
nginx
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
流程:
text
HTTP 请求
↓
Upgrade
↓
WebSocket 长连接
22. return 301 / 302 重定向
例如:
nginx
return 302 /mobile/;
表示:
浏览器临时跳转到当前域名下的
/mobile/。
当前域名:
text
https://www.test.com
最终:
text
https://www.test.com/mobile/
流程:
text
浏览器请求 /
↓
Nginx 返回:
302
Location: /mobile/
↓
浏览器重新请求
/mobile/
301 与 302
| 状态码 | 含义 | 常见场景 |
|---|---|---|
301 |
永久重定向 | 域名迁移、永久 URL 变更 |
302 |
临时重定向 | 设备分流、临时页面 |
例如:
nginx
return 302 https://m.test.com$request_uri;
用户访问:
text
https://www.test.com/news/1?id=10
会跳:
text
https://m.test.com/news/1?id=10
23. return 与 try_files 的区别
return 302
nginx
return 302 /mobile/;
属于浏览器重定向。
text
/
↓
浏览器收到 302
↓
浏览器重新请求
↓
/mobile/
地址栏发生变化。
try_files
nginx
try_files $uri $uri/ /index.html;
通常属于 Nginx 内部文件选择或内部重定向过程。
浏览器访问:
text
/user/list
Nginx 实际可能返回:
text
/index.html
但浏览器仍然保持:
text
/user/list
对照
| 指令 | 是否让浏览器重新请求 | 地址栏 |
|---|---|---|
return 301/302 |
是 | 变化 |
try_files |
通常否 | 通常不变化 |
24. rewrite URL 改写
固定跳转优先:
nginx
return 301 /new;
如果需要根据原 URL 动态修改:
nginx
rewrite ^/old/(.*)$ /new/$1 permanent;
例如:
text
/old/user
↓
/new/user
其中:
text
(.*)
捕获:
text
user
$1 就代表捕获的内容。
选择原则
text
固定跳转
→ return
需要正则替换 URL
→ rewrite
25. 移动端与 PC 端分流
Nginx 可以读取:
nginx
$http_user_agent
简单判断:
nginx
if ($http_user_agent ~* "(Android|iPhone|iPad|Mobile)") {
return 302 https://m.test.com$request_uri;
}
完整示例:
nginx
server {
server_name www.test.com;
location / {
if ($http_user_agent ~* "(Android|iPhone|iPad|Mobile)") {
return 302 https://m.test.com$request_uri;
}
root /home/html/pc;
index index.html;
}
}
流程:
text
请求 www.test.com
│
▼
读取 User-Agent
│
├── Mobile
│ ↓
│ 302 → m.test.com
│
└── PC
↓
返回 PC 页面
注意:User-Agent 可以伪造,不适合做真正的安全认证。
如果一套响应式页面可以解决问题,优先使用响应式。只有 PC/H5 是两套独立站点时,才更适合做服务端分流。
26. map 的使用
条件较多时,比到处写 if 更推荐使用 map。
map 通常配置在 http 层:
nginx
http {
map $http_user_agent $is_mobile {
default 0;
~*(Android|iPhone|iPad|Mobile) 1;
}
}
它的作用:
text
输入:
$http_user_agent
↓ map
输出:
$is_mobile
结果:
text
PC → $is_mobile = 0
Mobile → $is_mobile = 1
然后:
nginx
server {
server_name www.test.com;
location / {
if ($is_mobile = 1) {
return 302 https://m.test.com$request_uri;
}
root /home/html/pc;
}
}
同一域名,不同设备读取不同目录
nginx
map $http_user_agent $site_root {
default /home/html/pc;
~*(Android|iPhone|iPad|Mobile) /home/html/mobile;
}
然后:
nginx
location / {
root $site_root;
index index.html;
try_files $uri $uri/ /index.html;
}
效果:
text
PC
↓
/home/html/pc
Mobile
↓
/home/html/mobile
URL 可以不变化。
27. Nginx 中的简单 if
例如:
nginx
if ($is_mobile = 1) {
return 302 /mobile/;
}
这种简单:
text
if + return
通常比较清晰。
不要把 Nginx 的 if 当 JavaScript 业务代码使用。
复杂逻辑建议按照职责选择:
text
URL 匹配
→ location
条件映射
→ map
固定跳转
→ return
URL 规则修改
→ rewrite
文件存在判断
→ try_files
28. 常用判断符号
| 写法 | 含义 |
|---|---|
= |
相等 |
!= |
不相等 |
~ |
正则匹配,区分大小写 |
~* |
正则匹配,不区分大小写 |
!~ |
正则不匹配 |
!~* |
正则不匹配,不区分大小写 |
例如:
nginx
if ($host = "www.test.com") {
}
nginx
if ($http_user_agent ~* "iphone|android") {
}
29. 常用 Nginx 变量
29.1 URL 参数
请求:
text
/test?type=mobile&id=100
Nginx:
nginx
$arg_type
值:
text
mobile
nginx
$arg_id
值:
text
100
示例:
nginx
if ($arg_type = "mobile") {
return 302 /mobile/;
}
29.2 请求头
客户端:
http
X-App-Type: mobile
Nginx:
nginx
$http_x_app_type
例如:
nginx
if ($http_x_app_type = "mobile") {
return 302 /mobile/;
}
规律:
text
X-App-Type
↓
$http_x_app_type
Authorization
↓
$http_authorization
29.3 请求方法
nginx
$request_method
可能是:
text
GET
POST
PUT
DELETE
OPTIONS
例如:
nginx
if ($request_method = POST) {
return 403;
}
29.4 常用变量速查
| 变量 | 含义 |
|---|---|
$uri |
当前规范化 URI |
$request_uri |
原始请求 URI,通常包含 Query String |
$host |
当前 Host |
$scheme |
http / https |
$remote_addr |
客户端 IP |
$request_method |
GET / POST / PUT 等 |
$http_user_agent |
User-Agent |
$http_referer |
Referer |
$arg_id |
URL 参数 id |
$http_authorization |
Authorization Header |
例如:
text
https://www.test.com/user?id=100
大致:
text
$request_uri → /user?id=100
$uri → /user
$arg_id → 100
$host → www.test.com
30. HTTPS 基础配置
示例:
nginx
server {
listen 443 ssl;
server_name www.test.com;
ssl_certificate /etc/nginx/cert/www.test.com.pem;
ssl_certificate_key /etc/nginx/cert/www.test.com.key;
}
HTTP 跳 HTTPS:
nginx
server {
listen 80;
server_name www.test.com;
return 301 https://$host$request_uri;
}
访问:
text
http://www.test.com/oa/user
跳到:
text
https://www.test.com/oa/user
31. gzip 压缩
常用配置:
nginx
gzip on;
gzip_min_length 1k;
gzip_comp_level 6;
gzip_types
text/plain
text/css
application/json
application/javascript
application/xml;
流程:
text
JS / CSS / JSON / 文本
↓
Nginx
↓
gzip
↓
减少网络传输体积
图片、视频、zip 等很多格式本身已经压缩过,一般不需要依赖 gzip 再压一次。
32. 前端静态资源缓存
Vite 构建文件通常带 Hash:
text
index-a31f3.js
index-f83ad.css
这类文件适合长期缓存。
例如:
nginx
location ~* \.(js|css|png|jpg|jpeg|gif|svg|woff2?)$ {
expires 30d;
add_header Cache-Control "public";
}
但是:
text
index.html
通常不建议长缓存。
原因:
text
旧 index.html
↓
仍引用旧 JS Hash
↓
服务器已发布新版本
↓
旧 JS 可能已不存在
↓
页面白屏 / Chunk 加载失败
实际项目中通常会让 HTML 更新更及时,而带 Hash 的静态资源可以长缓存。
33. 日志与排错
访问日志:
bash
tail -f /var/log/nginx/access.log
错误日志:
bash
tail -f /var/log/nginx/error.log
如果遇到:
text
403
先看:
bash
tail -f /var/log/nginx/error.log
可能直接出现:
text
Permission denied
这时应继续排查:
- Linux 文件权限;
- Nginx 运行用户;
- SELinux;
- 目录执行权限;
- 文件上下文。
不要看到 403 就只改 Nginx location。
34. 三个必须掌握的 Nginx 命令
34.1 检查配置
bash
nginx -t
正常:
text
syntax is ok
test is successful
34.2 重新加载
bash
systemctl reload nginx
日常建议:
bash
nginx -t && systemctl reload nginx
比直接 restart 更适合日常改配置。
34.3 查看实际加载的完整配置
bash
nginx -T
这是非常重要的排错命令。
如果出现:
"我明明修改了配置,为什么没有生效?"
优先执行:
bash
nginx -T
检查:
server是否真的被加载;include是否漏写;- 是否存在多个同域名 server;
- location 最终配置是什么;
- 修改的是不是实际运行的配置文件。
35. 一套固定的 Nginx 排错流程
以后遇到:
- 403;
- 404;
- 502;
- Vue 刷新 404;
- API 404;
- 配置不生效;
可以按照下面顺序排查:
text
┌──────────────────────────────┐
│ 1. DNS / 域名是否指向正确服务器 │
└──────────────┬───────────────┘
↓
┌──────────────────────────────┐
│ 2. access.log 是否收到请求 │
└──────────────┬───────────────┘
↓
┌──────────────────────────────┐
│ 3. nginx -T 是否加载对应 server │
└──────────────┬───────────────┘
↓
┌──────────────────────────────┐
│ 4. 请求匹配哪个 location │
└──────────────┬───────────────┘
↓
┌──────┴──────┐
↓ ↓
静态文件 API
│ │
↓ ↓
root + URI? proxy_pass?
│ │
↓ ↓
文件存在? 最终 URI 对吗?
│ │
↓ ↓
权限/SELinux? 后端端口能 curl?
│ │
└──────┬──────┘
↓
┌──────────────────────────────┐
│ 5. Vue / Node 应用自身问题 │
└──────────────────────────────┘
常见错误与优先检查项
| 现象 | 优先检查 |
|---|---|
| 配置改了不生效 | nginx -T、include |
| Vue 首页正常,刷新 404 | try_files |
| JS/CSS 404 | Vite base、root + URI |
| API 404 | proxy_pass 最后的 / |
| 403 | Linux 权限、SELinux、error.log |
| 502 | 后端服务是否启动、端口是否正确 |
| WebSocket 失败 | Upgrade / Connection Header |
/oa 能开但 /oa/ 异常 |
location 与重定向规则 |
36. 完整实战配置
下面给出一份适合 Vue SPA + Node API 的完整示例。
目录结构
text
/home/html/project/
├── oa/
│ ├── index.html
│ └── assets/
│
└── crm/
├── index.html
└── assets/
Vue OA
ts
// vite.config.ts
export default defineConfig({
base: '/oa/',
})
ts
// router.ts
const router = createRouter({
history: createWebHistory('/oa/'),
routes,
})
Vue CRM
ts
// vite.config.ts
export default defineConfig({
base: '/crm/',
})
ts
// router.ts
const router = createRouter({
history: createWebHistory('/crm/'),
routes,
})
Nginx
nginx
server {
listen 443 ssl;
server_name www.test.com;
ssl_certificate /etc/nginx/cert/www.test.com.pem;
ssl_certificate_key /etc/nginx/cert/www.test.com.key;
# =========================
# OA
# =========================
location = /oa {
return 301 /oa/;
}
location ^~ /oa/ {
root /home/html/project;
index index.html;
try_files $uri $uri/ /oa/index.html;
}
# =========================
# CRM
# =========================
location = /crm {
return 301 /crm/;
}
location ^~ /crm/ {
root /home/html/project;
index index.html;
try_files $uri $uri/ /crm/index.html;
}
# =========================
# API
# =========================
location /api/ {
proxy_pass http://127.0.0.1:3000/;
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;
}
# =========================
# WebSocket
# =========================
location /ws/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
请求关系
text
https://www.test.com
│
├── /oa/*
│ ↓
│ OA Vue SPA
│
├── /crm/*
│ ↓
│ CRM Vue SPA
│
├── /api/*
│ ↓
│ Node / Koa :3000
│
└── /ws/*
↓
WebSocket
37. 核心知识速查表
| 知识点 | 核心理解 |
|---|---|
include |
加载其他配置文件 |
server |
根据域名、端口选择网站 |
location |
根据 URI 分流 |
location = /oa |
精确匹配 /oa |
location /oa/ |
匹配 /oa/ 下的路径 |
location ^~ /oa/ |
前缀匹配成功后优先使用 |
root |
root + 完整 URI |
alias |
用真实目录替换 location URL 前缀 |
index |
目录默认首页 |
try_files |
尝试文件,不存在时回退 |
createWebHashHistory |
# 后路由不发送给 Nginx |
createWebHistory |
真实 URL,需要服务器配合 |
Vite base |
控制静态资源基础路径 |
Router base |
控制 Vue 页面路由基础路径 |
proxy_pass |
反向代理到后端 |
return 301 |
永久重定向 |
return 302 |
临时重定向 |
rewrite |
按正则修改 URL |
map |
根据输入变量生成新的变量 |
$request_uri |
原始请求 URI |
$uri |
规范化 URI |
$http_user_agent |
User-Agent |
$arg_xxx |
获取 Query 参数 |
$http_xxx |
获取请求头 |
nginx -t |
检查配置语法 |
nginx -T |
查看真正加载的完整配置 |
reload |
平滑重新加载配置 |
access.log |
查看请求 |
error.log |
查看错误 |
总结
对于前端和 Node.js 开发,学习 Nginx 最重要的不是背大量配置,而是理解请求处理链:
text
浏览器
↓
server
↓
location
↓
判断怎么处理
│
├── 静态资源
│ ↓
│ root / alias
│ ↓
│ try_files
│
├── 后端接口
│ ↓
│ proxy_pass
│
└── 条件分流
↓
return / rewrite / map
而 Vue History 模式的核心链路是:
text
浏览器访问 /oa/user/list
↓
Nginx 找不到真实文件
↓
try_files 回退 /oa/index.html
↓
Vue 启动
↓
createWebHistory('/oa/')
↓
Vue Router 内部匹配 /user/list
↓
显示对应页面
真正理解这一条链以后,root、try_files、Vite base、Vue Router base 就不再是需要死记的配置,而是同一套 SPA 部署机制中的不同环节。
推荐后续学习
如果继续深入 Nginx,可以按照下面顺序学习:
text
Nginx 基础
↓
location 完整匹配优先级
↓
proxy_pass URI 重写规则
↓
缓存策略
↓
跨域 CORS
↓
WebSocket
↓
限流
↓
负载均衡 upstream
↓
灰度发布
这样基本可以覆盖前端、Node.js 项目绝大多数日常部署场景。