Vue/React SPA 打包部署后,子路由刷新 401 未登录问题彻底解决

今日遇到一个奇怪的问题,页面刷新就报登录失效,我刚登录也是报这个问题,经过仔细查找,发现SPA 单页项目部署 Nginx 后刷新子路由报错,算是老生常谈的经典坑了。但这次遇到的情况和常规 404 不一样,是子路由刷新直接报 401 未登录,坑点藏得更深,很多新手甚至中级前端都会被绕进去。

今天完整复盘一下这次线上真实问题,从现象、根因、踩坑点、最终落地方案一次性讲透,彻底根治 SPA 部署路由异常问题。

一、问题现象(线上真实场景)

项目技术栈:React + Vite + Java SpringBoot 后端,Nginx 反向代理部署。

线上环境出现诡异问题:

  • 网站首页 https://admin\.xxx\.com/ 访问、登录、所有功能完全正常

  • 登录后通过前端菜单跳转页面(如 /admin/model)一切正常

  • 只要手动刷新子路由页面,直接弹出 401 未登录/登录过期

返回报文就是后端标准拦截提示:{"code":401,"message":"未登录或登录已过期"}

最让人迷惑的一点:本地 Vite 开发环境 完全没有任何问题,只有打包上线后才复现。

二、初期误区(绝大多数人都会踩的坑)

刚开始排查的时候,我第一反应也是:是不是 token 刷新失效、cookie 丢失、登录态没存住?

反复检查了一遍前端逻辑:

  • localStorage 存储 token 正常

  • 请求拦截器统一携带 Authorization 请求头正常

  • 本地代理调试所有接口鉴权正常

前端逻辑毫无问题。

接着又怀疑后端拦截器规则问题,但后端接口本身鉴权是稳定的,正常页面跳转、接口请求全部不会 401,唯独刷新路由报错。

三、真正根因(核心本质)

这里必须搞懂一个绝大多数前端模糊的知识点:Vite 本地代理 和 线上 Nginx 代理完全是两套逻辑。

本地开发时:

浏览器访问 localhost:5175/admin/model,是 Vite 服务接管路由,前端路由匹配页面,接口走 Vite 代理转发到 Java 8082 端口。

页面路由不会经过后端,只有接口会经过后端,所以永远不会 401。

线上部署时,出问题的根源就在最初的 Nginx 配置:

最初配置是全量代理,所有请求(不管是页面路由还是接口)全部转发给 Java 后端:

浏览器刷新https://admin.xxx.com/admin/model ➜ Nginx 直接转发到 Java 服务

Java 后端的逻辑是:所有非接口路径,没有对应接口,统一走全局拦截器鉴权。

后端把 /admin/model 当成了一个后端接口请求,而不是前端页面路由,没有登录态,直接拦截返回 401。

一句话总结根因:

SPA 前端路由刷新,被 Nginx 错误转发给 Java 后端,后端鉴权拦截器误拦截页面 GET 请求,导致刷新子路由 401。

四、理清架构:页面路由 和 接口路由 必须拆分

我们这套项目架构非常典型:

  • 前端:纯静态 SPA 资源(打包后 index.html + js/css)

  • 后端:Java SpringBoot 只负责提供接口,不负责页面渲染

  • 接口分为两类:/api/xxx 业务接口、/auth/xxx 登录鉴权接口

正确的线上转发逻辑必须是:

  • 所有页面路由(/、/admin、/admin/model...):交给 Nginx,返回 index.html,由前端 JS 路由解析

  • 所有接口路由(/api、/auth):反向代理转发给 Java 8082 后端

之前的致命错误就是:页面路由也被当成接口转发给后端了。

五、最终落地解决方案(生产可用、稳定根治)

1、Nginx 核心配置修正(关键)

拆分路由规则,页面走静态兜底,接口走后端代理,彻底解决刷新 401/404 问题:

单独代理后端两类接口,其余全部交给前端 SPA 路由:

nginx 复制代码
# 业务接口代理
location /api/ {
    proxy_pass http://127.0.0.1:8082;
    proxy_http_version 1.1;
    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;
}

# 登录鉴权接口代理(对应Vite本地/auth代理)
location /auth/ {
    proxy_pass http://127.0.0.1:8082;
    proxy_http_version 1.1;
    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;
}

# 所有页面路由兜底,解决SPA刷新问题
location / {
    try_files $uri $uri/ /index.html;
}

2、本地 Vite 配置保留(无需改动)

本地开发代理逻辑正常,保留双代理配置,兼顾业务接口和登录鉴权接口:

typescript 复制代码
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

export default defineConfig({
  plugins: [react()],
  server: {
    port: 5175,
    host: true,
    proxy: {
      '/api': {
        target: 'http://localhost:8082',
        changeOrigin: true,
        rewrite: (p) => p,
      },
      '/auth': {
        target: 'http://localhost:8082',
        changeOrigin: true,
      },
    },
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
  },
});

六、改完之后的正确逻辑

1、刷新 /admin/model

Nginx 找不到对应静态文件 → 兜底返回 index.html → 前端 JS 加载后自行匹配路由 → 不会请求后端,无 401

2、页面发起登录、业务请求

/auth/login、/api/xxx 匹配 Nginx 代理规则 → 正常转发 Java 后端 → 正常鉴权、正常返回数据

七、开发多年总结的 SPA 部署核心经验

1、永远不要让 Nginx 把前端路由转发给后端接口服务,这是 90% SPA 部署诡异报错的根源。

2、Vite、Webpack 的本地代理只在开发环境生效,打包后完全失效,千万不要用本地开发逻辑去臆测线上环境。

3、常规刷新 404 是路由找不到,刷新 401 一定是页面路由被后端拦截器拦截,优先排查 Nginx 转发规则,而非前端 token。

4、前后端分离架构,必须严格区分:Nginx 管页面,Java 管接口,职责边界一旦混乱,线上必出诡异问题。

八、最终效果

修改 Nginx 配置并重载后:

  • 所有子路由页面刷新正常,不再 401

  • 登录、登出、token 刷新、所有业务接口完全正常

  • 首页、嵌套路由、多级路由全部稳定运行

问题彻底根治,线上稳定无复发。

相关推荐
旋生万物1 小时前
素数螺旋映射 $z_n=n^{1+i}$ 的角分布统计检验与零模型对比
大数据·前端·人工智能·算法·云原生·螺旋生成论·螺旋相位
福兮说1 小时前
JS 正则的六个坑:带 g 的 test() 一真一假、空匹配死循环、replace 里的 $
开发语言·前端·javascript·正则表达式
码艺-Alimjan1 小时前
Web 网站打包桌面应用的另一种方式,超级简单(C# exe 33Kb)
开发语言·前端·c#
Tyrvision1 小时前
新品发布前,三维动画怎么排期才不挤在最后一周?
前端
miss2 小时前
从零做一个可视化规则引擎:Vue3 递归条件树 + 双引擎结果对比
前端·vue.js·typescript
Daorigin_com2 小时前
道本科技携手DeepSeek:以AI重塑合同全生命周期管理
前端·人工智能·科技·网络安全·数据挖掘·前端框架·传媒
flash俊杰2 小时前
自动更新工程:electron-updater、差分更新、灰度发布与失败回滚
前端
fastjson_2 小时前
帆软看板 - 问题收集
linux·前端·javascript