问题描述
最近在处理一个 Vue 项目的登录重定向问题时,遇到了个 bug 久久未能解决,原本以为是重定向函数的处理有问题,但通过几轮 debug 后,发现背后另有玄机...
Vue 的路由模式有 HTML5,Hash 和 Memory(在 Vue2 中为 abstract) 三种模式,公司的登录项目原本是使用 Memory 模式,有个 API_PREFIX_URL 环境变量,专门为客户提供定制化的请求前缀,该变量最终会其赋值给 domainCustomPrefix 变量后再把它写入到 meta 里(至于为什么是这个传递链路,不要在意这些细节(ˉ▽ˉ;)...),最终通过自定义的工具函数拿到自定义 API 前缀:getMetaDataByName('domainCustomPrefix');
然后有意思的来了,某位客户私有化部署后收到异常反馈:访问domain.com/miniapp/login时,地址栏竟自动跳转为domain.com/login/miniapp/login,导致页面404。该客户环境配置了API_PREFIX_URL='/miniapp',而通常情况下该值为空。
技术上下文
// main.ts
// 改造前(内存路由)
createRouter({
history: createMemoryHistory(),
routes: [...]
});
// 改造后(HTML5模式)
createRouter({
history: createWebHistory(import.meta.env.DEV ? '/' : '/login'),
routes: [...]
});
问题定位
为什么当 domainCustomPrefix 存在值时(如/miniapp),会在 /miniapp 前多了个 login ,这使我百思不得其解,查阅 Vue Router 文档后,发现它专门提供了处理子目录部署的机制:
If you deploy to a subfolder, you should use the publicPath option of the CLI and the related base property of the router.
其运作原理类似于:
实际路由路径 = location.pathname.replace(base, '')
由此我们便发现了一个关键的线索:原配置硬编码了/login路径,Vue Router 的 base 参数未动态适配环境变量。
所以,我们需要基于该线索,尝试以以下思路解决问题:
- 统一路由基础路径(base)
- 建立
domainCustomPrefix与路由base的动态映射
解决方案
- 获取基础路径
const routerBase = domainCustomPrefix ? `${domainCustomPrefix}/login` : '/login';
- 在 Vue Router 中使用该路径
// main.ts
createRouter({
history: createWebHistory(routerBase),
routes: [...]
});
原理解析
- createWebHistory 特性
- createWebHistory(base) 会把 base 参数作为所有路由的基础路径
- Vue Router 会自动处理这个基础路径,不会重复添加;
- .e.g.,当前 routerbase 为
/miniapp/login,访问路domain.com/miniapp/login时,- Vue Router 发现当前 URL 已经包含了这个 base,所以不会重复添加,而是把它作为基础路径,后续的路由都会基于这个基础路径
- .e.g.,当前 routerbase 为
- 为什么之前用 Memory 模式没问题
- 路由完全基于内存,不会修改浏览器的 URL
- 当使用
location.href跳转时,完全是一个新的导航,不会被 Vue Router 处理
小结
- 使用 HTML5 模式时,注意 base 路径配置
- 避免硬编码路径,使用动态配置
- 确保访问地址与 routeBase 一致