【避坑指南】Vue 部署 Nginx 后,路由访问“串台”到了后端?刷新 404 怎么办?

3 阅读6分钟

很多同学在将 Vue 项目打包部署到 Nginx 后,会遇到两个经典问题:一是首页能打开,但刷新页面直接 404;二是点击路由跳转正常,但浏览器地址栏输入特定路由后,请求竟然被转发到了后端接口,导致报错。本文将深入剖析 Vue Router 的 History 模式原理,提供标准的 Nginx 配置模板,并教你如何通过 location 优先级彻底解决前后端路由冲突。


🚨 问题场景重现

假设你的项目结构如下:

  • 前端:Vue 3 + Vue Router (History 模式)
  • 后端:SpringBoot / Node.js / Go (运行在 8080 端口)
  • 网关:Nginx (运行在 80 端口,反向代理后端,同时托管前端静态文件)

❌ 现象一:刷新变 404

用户在首页 http://www.example.com/ 访问正常,点击菜单进入 http://www.example.com/user/profile 也正常。 但是,当用户在 user/profile 页面刷新浏览器,或者直接在地址栏输入该链接回车时,Nginx 返回 404 Not Found

❌ 现象二:路由“串台”到后端

更诡异的是,有时候刷新没有报 404,而是报了一个 502 Bad Gateway 或者后端定义的 404 JSON 错误。 查看 Nginx 日志发现,请求 /user/profile 竟然被 proxy_pass 转发给了后端服务器!后端当然找不到这个 HTML 路径,于是报错。

核心原因:Nginx 的配置顺序写错了,或者没有正确理解 Vue Router 的 History 模式机制。


🔍 原理剖析:为什么会出现这个问题?

1. Vue Router 的 History 模式

Vue Router 默认有 hash 模式(URL 带 #)和 history 模式(URL 优雅,不带 #)。

  • Hash 模式# 后面的内容不会发送给服务器,所以服务器永远只接收 /,不会出错。
  • History 模式:利用 HTML5 的 pushState API。当你访问 /user/profile 时,浏览器确实向服务器发起了一个 GET 请求 /user/profile

关键点:服务器上实际上并没有 /user/profile 这个文件或文件夹。所有的路由逻辑都在前端的 js 包里。 因此,无论用户访问什么路径,Nginx 都必须返回同一个文件:index.html,让 Vue 接管后续的路由渲染。

2. Nginx 的匹配陷阱

很多配置文件是这样写的:

# ❌ 错误示范
location / {
    proxy_pass http://backend_server; # 优先把所有请求给后端
}

location / {
    root /usr/share/nginx/html; # 这一行可能根本不会生效,或者被上面的覆盖
    try_files $uri $uri/ /index.html;
}

或者,虽然写了 try_files,但是 proxy_pass 的 location 优先级更高,导致前端路由路径被当作后端接口处理了。


✅ 解决方案:标准 Nginx 配置模板

要解决这个问题,必须遵循一个核心原则:“静态资源优先,未知路径回退 index.html,特定接口转发后端”

以下是经过生产环境验证的 nginx.conf 配置片段:

server {
    listen       80;
    server_name  www.example.com;

    # 1. 根路径:处理前端所有路由
    location / {
        root   /usr/share/nginx/html; # 你的 Vue 打包文件存放路径
        index  index.html index.htm;
        
        # 🔑 核心配置:
        # 如果请求的文件($uri)存在,直接返回;
        # 如果请求的目录($uri/)存在,直接返回;
        # 如果都不存在(说明是前端路由),强制返回 /index.html
        try_files $uri $uri/ /index.html;
    }

    # 2. 接口路径:专门代理后端请求
    # 假设你的后端接口都以 /api 开头
    location /api/ {
        # 注意末尾的斜杠,涉及路径重写
        proxy_pass http://127.0.0.1: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;
        
        # 解决跨域问题(如果需要)
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
    }

    # 3. 静态资源缓存优化(可选)
    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        root /usr/share/nginx/html;
        expires 30d; # 缓存 30 天
        add_header Cache-Control "public, immutable";
    }

    # 4. 错误页面处理(可选)
    error_page 404 /index.html;
}

💡 配置详解

  1. try_files $uri $uri/ /index.html; 这是解决“刷新 404”的神器。

    • 当用户访问 /user/profile
      • Nginx 找 /usr/share/nginx/html/user/profile 文件?不存在。
      • /usr/share/nginx/html/user/profile/ 目录?不存在。
      • 执行最后一步:内部重定向到 /index.html
    • 浏览器拿到 index.html,Vue 加载 JS,解析 URL 为 /user/profile,渲染对应组件。✅
  2. location /api/ 的优先级 Nginx 匹配 location 时,精确匹配 > 最长前缀匹配

    • 请求 /api/user/list -> 匹配 location /api/ -> 转发后端。✅
    • 请求 /user/profile -> 不匹配 /api/ -> 落入 location / -> 返回 index.html。✅

    注意:一定要把具体的接口 location 写在通用的 location / 之前吗? 其实对于前缀匹配,Nginx 会自动选择匹配度最高的(最长的)。但为了代码可读性和避免正则干扰,建议将具体的 proxy_pass 块放在前面,或者确保没有正则表达式干扰前缀匹配逻辑。在上述配置中,/api// 更长,优先级天然更高。


🛠️ 进阶场景:后端接口没有 /api 前缀怎么办?

如果你的后端接口是 RESTful 风格,直接就是 /users, /orders,这就和前端路由 /users (用户中心) 冲突了!

情况 A:前端路由和后端接口路径完全重合 例如:前端有个页面叫 /login,后端也有个接口叫 /login

  • 后果:Nginx 无法区分你是想看登录页,还是想调登录接口。
  • 解决
    1. 修改前端:给所有 API 请求加上统一前缀(如 /api),并在 vite.config.jsvue.config.js 中配置 proxy。这是最推荐的做法。
    2. 修改后端:给所有接口加上前缀(如 /api/v1)。
    3. 下策(Nginx 强行区分):通过 HTTP Method 区分(GET 走前端,POST 走后端),但这不符合 REST 规范且容易出错,强烈不推荐

情况 B:使用 Nginx 区分文件类型(极少用) 如果后端返回 JSON,前端返回 HTML。你可以尝试判断后缀,但这非常脆弱,因为现代前端路由通常没有后缀。

结论请务必在前端代码中统一添加 API 前缀(如 /api),这是解决路由冲突的根本之道。


🧪 如何验证配置是否生效?

  1. 检查语法

    nginx -t
    

    确保输出 syntax is oktest is successful

  2. 重载配置

    nginx -s reload
    
  3. 测试步骤

    • 访问首页 http://your-domain.com/ -> 应正常。
    • 点击进入二级页面 http://your-domain.com/about -> 应正常。
    • 关键步骤:在 about 页面按 F5 刷新 -> 应正常显示页面,而不是 404 或后端报错
    • 调用接口 http://your-domain.com/api/data -> 应返回后端 JSON 数据。

📝 常见 Q&A

Q: 我加了 try_files 还是 404? A: 检查 root 路径是否正确。Nginx 的用户权限是否有读取该文件夹的权限?查看 error.log (/var/log/nginx/error.log) 是最快的方法。

Q: 为什么本地 npm run serve 没问题,上线就不行? A: 本地开发服务器(Vite/Webpack Dev Server)内置了 fallback 机制(自动处理 history 模式),而生产环境的 Nginx 是纯静态服务器,不会自动帮你补全 index.html,必须手动配置。

Q: 子目录部署怎么办? A: 如果你的站点是 http://domain.com/my-app/

  • Vue 配置:base: '/my-app/'
  • Nginx 配置:
    location /my-app/ {
        alias /usr/share/nginx/html/my-app/; # 注意用 alias
        try_files $uri $uri/ /my-app/index.html; # 回退路径要带前缀
    }
    

🎯 总结

Vue 项目部署 Nginx 出现路由问题,90% 都是因为缺少了 try_files $uri $uri/ /index.html; 这行配置,或者是接口路径前缀规划不当导致请求被错误转发。

记住这个黄金公式:

静态资源 + 前端路由 = try_files 回退到 index.html 后端接口 = 独立 location 进行 proxy_pass

配置好后,你的 Vue 应用就能像原生 App 一样流畅运行,不再担心刷新掉线或请求串台了!

如果觉得这篇博客帮到了你,欢迎点赞、收藏、评论三连支持!有任何 Nginx 配置疑问,欢迎在评论区留言讨论。