微信自定义菜单是公众号引流到H5应用的主要入口之一,运营人员在公众号后台配置一个菜单,点击后打开我们用Vue开发的H5页面。这条链路看似简单,实际做起来却经常翻车:页面白屏、路由匹配不上、URL参数莫名丢失、OAuth授权回来后跳到首页等等。这些问题十有八九和Vue Router的路由模式选择有关。本文把微信环境下的路由配置要点梳理清楚,并给出可直接使用的配置方案。

为什么微信内的路由模式要慎重选择
Vue Router默认使用hash模式,URL形如https://xxx.com/index.html#/home,井号后面的部分不会发送到服务器,路由切换完全在前端完成。而history模式去掉了井号,URL变成https://xxx.com/home,看起来更干净,但依赖浏览器的History API,并且要求服务器配合做回退处理。
微信内置浏览器基于X5内核(安卓端)或WKWebView(iOS端),对History API的支持存在不少边缘问题。最典型的是:从自定义菜单跳转history模式的链接时,iOS端可能出现首次进入正常、内部跳转后再返回时路由错乱的情况;更常见的是安卓X5内核下,使用router.push后URL变化但分享出去的链接和实际路径不一致,导致二次分享打不开页面。
所以在微信场景下,除非有强制要求,hash模式是更稳妥的选择。hash模式不依赖服务器配置,微信菜单里直接填完整的带井号链接即可,跳转行为可预期,排查问题也简单。
hash模式的具体配置与菜单链接拼接
先看路由的基本配置,创建路由实例时显式声明mode: 'hash'(Vue Router 3.x):
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
mode: 'hash',
base: '/',
routes: [
{
path: '/',
redirect: '/home'
},
{
path: '/home',
name: 'Home',
component: () => import('@/views/Home.vue')
},
{
path: '/activity/:id',
name: 'Activity',
component: () => import('@/views/Activity.vue')
}
]
})
如果项目用的是Vue Router 4.x(Vue 3),写法改为createWebHashHistory:
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: () => import('./views/Home.vue') }
]
})
export default router
配置完成后,微信自定义菜单里填写的链接应该是完整的hash地址,例如https://xxx.com/h5/index.html#/activity/88。注意微信菜单链接必须是通过域名安全校验的地址,域名需要在公众号后台的网页授权域名和JS接口安全域名中都配置好。这里有个细节:hash部分的路径参数(如/activity/88中的88)在微信菜单配置时不会被截断,因为微信只校验问号之前的查询参数部分,井号后面的内容会原样传递给浏览器。
必须用history模式时该怎么处理
有些项目因为SEO或者已有线上链路的原因必须用history模式,那就要做好两件事。第一是路由配置:
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{ path: '/home', component: () => import('@/views/Home.vue') }
]
})
第二是服务器端必须把所有路径回退到index.html,否则用户在微信里直接打开https://xxx.com/home会返回404。nginx的典型写法:
location /h5/ {
try_files $uri $uri/ /h5/index.html;
}
同时vue.config.js中的publicPath要与部署目录一致,比如部署在/h5/目录下就配置publicPath: '/h5/',否则打包后静态资源路径会指向站点根目录,微信内直接白屏。另外要注意,history模式在微信内做OAuth授权时,授权回调地址不要带井号,且回调参数code会拼在URL后面,如果路由使用了严格模式或路径带了尾部斜杠,可能出现匹配失败,建议加一个通配路由兜底并记录错误日志。
OAuth授权回跳与参数丢失的坑
从菜单进入的页面往往需要获取用户身份,走微信OAuth授权流程。授权回来的URL会被微信追加code和https://xxx.com/index.html?code=xxx&state=STATE#/home,注意code插在了井号前面,取参数时要小心,this.$route.query拿到的是井号后的查询串,井号前的参数需要手动从window.location.search里解析:
// 从location.search中解析code参数
function getQueryParam(name) {
const match = window.location.search.match(new RegExp('[?&]' + name + '=([^&]*)'))
return match ? match[1] : null
}
const code = getQueryParam('code')
if (code) {
// 已授权回调,用code换取用户信息
exchangeCodeForToken(code)
} else {
// 未授权,跳转到授权页
const redirectUri = encodeURIComponent('https://xxx.com/h5/index.html')
location.href = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID' +
'&redirect_uri=' + redirectUri +
'&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect'
}
还有一类高频问题是iOS和安卓行为不一致:iOS的微信WebView会记住第一次进入应用时的URL,导致后续调用window.location.href修改hash不生效或JS-SDK签名用的URL不对。解决办法是在真正需要更新URL时用location.replace,或者对iOS单独处理,JS-SDK签名时取window.entryUrl(首次进入时记录的URL)。安卓端则用当前location.href即可。这类差异建议在入口文件里统一封装:
// main.js 入口处记录初始URL,供iOS签名使用
window.entryUrl = window.location.href.split('#')[0]
router.afterEach(() => {
// 每次路由切换后,安卓端刷新签名所需的URL记录
if (isAndroid()) {
window.entryUrl = window.location.href.split('#')[0]
}
})
排查问题的几个实用手段
微信内调试不像普通浏览器那么方便,推荐借助vConsole:在入口处动态引入,生产环境可通过URL参数控制是否开启。引入后能直接在微信里看console输出、网络请求和路由跳转日志,绝大多数白屏和路由问题都能定位。
另一个技巧是在router.onError里注册错误回调,把路由异常上报到监控平台。同时检查公众号后台的网页授权域名是否与实际访问域名完全一致(协议、域名、端口都要匹配),授权域名不一致时微信会直接提示redirect_uri参数错误,这是新手最常踩的坑之一。把路由模式、授权域名、服务器回退这三项都确认无误后,自定义菜单到H5页面的整条链路基本就能稳定跑通了。