常见问题
排查菜单、动态路由、刷新 404、代理、token、Vite 预设构建和 devtools 初始化顺序问题
这一页用于快速定位 apps/admin 二次开发时最常见的问题。每个问题都先给排查路径,再指向更完整的专题页面。
如果一个问题同时涉及路由、菜单、权限和请求,优先按当前启动链路理解:
main.tsx
-> bootstrap.tsx
-> setupTheme()
-> setupAdminLayouts()
-> setupAdminPlugins()
-> setupI18n()
-> render <App />
App.tsx
-> QueryClientProvider
-> Jotai Provider
-> AdminDevtoolsProvider
-> AntdProvider
-> NotificationProvider
-> RouterProvider快速索引
| 现象 | 先看哪里 |
|---|---|
| 菜单不显示 | VITE_AUTH_ROUTE_MODE、setupAdminLayouts、useAuth().initAuth()、页面 staticData.menu |
| 动态路由点击后 404 | 后端路由 path 是否有前端页面文件和 routeTree.gen.ts 对应 |
| 刷新页面 404 | 生产静态服务是否配置 history rewrite |
| 代理失败 | VITE_HTTP_PROXY、VITE_SERVICE_BASE_URL、VITE_OTHER_SERVICE_BASE_URL、是否重启 Vite |
| token 异常 | localStg、setAuth()、请求 adapter、业务 code 配置 |
| 启动时 Vite 预设未构建 | predev / prebuild 是否执行了 build:admin-vite |
| devtools 或 NProgress 顺序异常 | main.tsx、bootstrap.tsx、setupAdminPlugins() 的执行顺序 |
菜单不显示
先判断当前是静态菜单还是动态菜单:
VITE_AUTH_ROUTE_MODE=static当前默认是 static。静态模式下,菜单来自前端 routeTree 和路由 staticData;动态模式下,菜单来自 fetchGetBackendRoutes() 返回的后端路由树,再结合前端 route tree 做权限和路径匹配。
静态模式排查
| 检查项 | 说明 |
|---|---|
| 页面位置 | 后台页面应放在 apps/admin/src/pages/(admin) 下。 |
| 路由生成 | apps/admin/src/features/router/routeTree.gen.ts 是否包含新页面。 |
| 元信息 | 页面 createFileRoute() 是否配置了 staticData。 |
| 菜单配置 | staticData.menu 是否存在,且没有被设为隐藏。 |
| 权限 | staticData.permissions 是否允许当前用户角色,或当前用户是否有 VITE_STATIC_SUPER_ROLE。 |
| 分类 | 当前 menuCategories 只有 admin -> /(admin),页面是否挂在这个后台 layout 下。 |
| 初始化 | 登录后是否成功执行 useAuth().initAuth(),并调用 initMenus(data)。 |
最小页面结构参考:
import { createFileRoute } from '@tanstack/react-router';
const DemoPage = () => {
return <div>Demo</div>;
};
export const Route = createFileRoute('/(admin)/demo')({
component: DemoPage,
staticData: {
i18nKey: 'route.demo',
icon: 'mdi:view-dashboard',
menu: {
order: 10
},
title: 'Demo'
}
});如果页面能直接访问但菜单不出现,通常是 staticData.menu、权限或菜单分类问题。如果页面本身也 404,先排查路由文件和 routeTree.gen.ts。
动态模式排查
动态模式要同时满足两件事:
- 后端接口返回了可访问的菜单路由。
- 前端存在同路径页面,route tree 能匹配它。
相关实现位置:
| 文件 | 职责 |
|---|---|
apps/admin/src/service/api/route/api.ts | fetchGetBackendRoutes() 请求 /route/getReactUserRoutes。 |
packages/@core/types/src/api/route.d.ts | 定义后端原始路由响应和适配后的动态路由结构。 |
apps/admin/src/features/menus/dynamic-routes.ts | 把后端 name/path/component/handle 响应适配成布局运行时需要的动态菜单数据。 |
apps/admin/src/bootstrap.tsx | 通过 loadDynamicRoutes 把 loadAdminDynamicRoutes() 注入布局运行时。 |
packages/web/admin-layouts/src/features/menus/* | 根据动态路由、权限和 route tree 生成菜单。 |
后端返回的 path 必须能和前端 apps/admin/src/pages 生成的 route tree 对上。只返回菜单数据但前端没有对应页面文件,点击后仍然会 404。
动态路由失效
动态路由失效通常不是一个单点问题,要按四层看:
| 层级 | 关键问题 |
|---|---|
| env | VITE_AUTH_ROUTE_MODE 是否为 dynamic。 |
| 接口 | /route/getReactUserRoutes 是否返回当前用户的路由树。 |
| 前端页面 | apps/admin/src/pages 下是否存在对应 path 的页面。 |
| 权限 | permissions、用户角色、hasAuthorizedRoutePath() 是否允许访问。 |
如果菜单展示了,但点击进入 404,优先检查后端返回的 path 和前端路由路径是否一致。动态菜单不是运行时动态加载任意页面,它只是决定“哪些已存在的前端页面可见、可访问”。
如果菜单不展示,先看 queryMenusOptions() 是否真正发起请求。当前实现只有在已有用户信息时才启用菜单查询:
const enabled = Boolean(queryClient.getQueryData(AUTH_QUERY_KEYS.USER_INFO));因此用户信息初始化失败时,动态菜单也不会生成。
刷新页面 404
开发环境由 Vite dev server 承接前端路由。生产环境如果使用 history 路由,刷新深层路径时必须由静态服务回退到 index.html。
正确的 Nginx 根路径配置:
location / {
try_files $uri $uri/ /index.html;
}子路径部署时,回退路径也要带子路径:
location /admin/ {
try_files $uri $uri/ /admin/index.html;
}同时确认构建 base:
VITE_BASE_URL=/admin/只配置 rewrite,不配置正确的 VITE_BASE_URL,会导致刷新能回到 index.html,但静态资源仍然 404。
代理失败
先区分开发代理和生产代理。
开发代理只在 vite dev 中生效:
VITE_HTTP_PROXY=Y
VITE_PROXY_LOG=Y
VITE_SERVICE_BASE_URL=https://mock.apifox.cn/m1/3109515-0-default
VITE_OTHER_SERVICE_BASE_URL={ demo: "http://localhost:9528" }请求层在开发环境且 VITE_HTTP_PROXY=Y 时,会把主服务改成:
/proxy-default其他服务按 key 改成:
/proxy-{key}@skyroc/web-admin-vite 再在 Vite dev server 中把这些前缀转发到真实地址。
常见检查项:
| 现象 | 检查 |
|---|---|
请求没有走 /proxy-default | 当前是否是 dev 环境,VITE_HTTP_PROXY 是否为 Y。 |
| 终端没有代理日志 | VITE_PROXY_LOG 是否为 Y。 |
| 其他服务代理不存在 | VITE_OTHER_SERVICE_BASE_URL 是否是合法 JSON5,key 是否和请求实例使用的 key 一致。 |
| 改 env 后仍旧请求旧地址 | Vite env 启动时读取,改完要重启 dev server。 |
生产环境 /proxy-default 404 | 生产没有 Vite dev server proxy,改 .env.prod 或配置 Nginx 反向代理。 |
token 异常
当前认证状态分为三块:
| 位置 | 职责 |
|---|---|
apps/admin/src/features/auth/use-auth.ts | Jotai auth state,登录后保存 token,退出时清理状态和菜单。 |
apps/admin/src/features/auth/shared.ts | 从 localStg 读取和清理 token。 |
apps/admin/src/service/adapter.ts | 请求 adapter,提供 getToken()、getRefreshToken()、setAuth()、resetAuth()、redirectToLogin()。 |
packages/@core/service/src/request/* | 统一添加 Authorization,处理登出码、弹窗登出码和 token 过期刷新。 |
登录成功后会执行:
setAuth(data);
const info = await initAuth();setAuth() 会保存:
token
refreshToken请求层会把 token 转成:
Authorization: Bearer {token}如果登录后马上又回到登录页,先检查:
| 检查项 | 说明 |
|---|---|
| 登录接口返回 | 是否包含 token 和 refreshToken。 |
| 用户信息接口 | initAuth() 内的 queryUserInfoOptions() 是否成功。 |
| 存储前缀 | VITE_STORAGE_PREFIX 是否变化,导致读写的 localStorage key 不一致。 |
| 业务 code | VITE_SERVICE_SUCCESS_CODE 是否和后端成功 code 一致。 |
| 登出 code | VITE_SERVICE_LOGOUT_CODES、VITE_SERVICE_MODAL_LOGOUT_CODES 是否误命中。 |
| 过期 code | VITE_SERVICE_EXPIRED_TOKEN_CODES 是否和后端 token 过期 code 一致。 |
如果刷新后变成未登录,检查浏览器 localStorage 中对应 VITE_STORAGE_PREFIX 下的 token 是否存在。
Vite 预设未构建
apps/admin/vite.config.ts 从 @skyroc/web-admin-vite 导入:
import { defineConfig } from '@skyroc/web-admin-vite';因此启动和构建前都需要先构建这个 workspace 包。apps/admin/package.json 已经配置了:
{
"predev": "pnpm run build:admin-vite",
"prebuild": "pnpm run build:admin-vite",
"prepreview": "pnpm run build:admin-vite"
}推荐命令:
pnpm --filter skyroc-admin dev
pnpm --filter skyroc-admin build
pnpm --filter skyroc-admin preview如果你绕过 package script 直接运行 vite,就不会自动触发这些 pre* 脚本。遇到预设产物缺失时,先执行:
pnpm --filter skyroc-admin build:admin-vitedevtools 初始化顺序
当前入口顺序是有意设计的:
| 阶段 | 位置 | 作用 |
|---|---|---|
| 1 | main.tsx | 开发环境先加载 @skyroc/web-admin-devtools/jotai,再进入 bootstrap.tsx。 |
| 2 | bootstrap.tsx | 初始化 theme、layout runtime、plugins、i18n。 |
| 3 | App.tsx | 挂载 Query、Jotai、AdminDevtools、Antd、Notification、Router。 |
如果 devtools、Jotai 面板或 Router 面板异常,先确认是否改动了入口顺序。@skyroc/web-admin-devtools/jotai 需要在应用 Provider 挂载前加载,否则 Jotai 调试能力可能拿不到正确上下文。
NProgress 也依赖初始化顺序。globalConfig.nprogress 读取前,必须先由 setupAdminPlugins() 调用 initNProgress()。如果提前读取,会触发:
nprogress is not initialized, please call initNProgress function first这类问题不要通过吞错误处理。应回到 bootstrap.tsx 和 plugins 初始化链路,确认初始化发生在路由和页面副作用之前。
修改后仍无效
按这个顺序缩小范围:
- 确认改的是
apps/admin当前使用的文件,不是旧 Vue 文档或旧 React 文档里的路径。 - 确认 env 修改后已经重启 dev server,生产 env 修改后已经重新 build。
- 确认
routeTree.gen.ts已经随页面文件变化重新生成。 - 确认登录用户信息、角色、权限和后端动态路由数据符合预期。
- 确认问题发生在开发环境还是生产静态服务,代理和 rewrite 的排查路径不同。