Changelog
All notable changes to this project are documented here. The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[2.7.1] - 2026-08-31
Fixed
- H5 back navigation infinite loop ("unable to go back, keeps flickering") - Fixed the infinite loop / flicker between adjacent pages caused by the
onBeforeBackpopstate back guard on the H5 platform (issue #39)- Symptom: After entering Home → Level 2 → Level 3 on H5, performing back (
router.back()or browser back) from Level 3 caused the page to rapidly switch back and forth between Level 2 and Level 3 without ever returning, with no JS errors in the console - Root cause: The H5 back guard previously used the strategy "
history.go(1)to undo the back → re-runnavigateBackafter the guard passes". On H5,navigateBackfires multiplepopstateevents; when these self-triggeredpopstateevents were misidentified as "new external backs" due to delayed dispatch timing, the code re-entered the "undo + replay" branch, forming an infinite loop - Fix: Before calling
router.back()and any back after the guard passes, set an H5 in-progress back flag and allow everypopstateproduced by this navigation within a time window (no longer entering the "undo + replay" branch); also track the target back path — matching the target URL marks the back complete (deterministic termination), with the time window as a fallback that auto-resets - Guard semantics preserved: When the guard passes, it returns to the previous page normally; when the guard aborts, it stays on the current page. No more infinite loops in either case
- Affected files:
router/back-guard.ts(H5 in-progress back flag + target-match detection)、router/index.ts(pre-flag inback())
- Symptom: After entering Home → Level 2 → Level 3 on H5, performing back (
[2.7.0] - 2026-08-30
Added
- H5 navigation animations (CSS transitions) - Extended navigation animation capability from the App platform to H5, using injected keyframe CSS to produce transitions aligned in naming with the App-side
animationType(based ontransform/opacity)- After a successful
push(uni.navigateTo), plays the enter animation (animatePageEnter) on the target page, deferred to the next frame viarequestAnimationFrameto wait for the page to finish rendering - For
back(uni.navigateBack), first plays the exit animation (animatePageExit) on the current page, then performs the actual back after the animation ends, matching the App-side slide-out effect - Supports directional keyframes such as
slide-in/out-*,fade-in/out,zoom,pop; styles are auto-cleaned after the animation ends (animationend), with a timer fallback to avoid residue during fast page switches - Default duration
300ms(DEFAULT_ANIMATION_DURATION), overridable viaduration
- After a successful
plugins/animation/h5.tsmodule - H5 animation style injection (idempotent) and enter/exit animation playback logic. Since the npm build is produced by tsup and does not process#ifdef H5conditional compilation, it uses the runtimegetPlatform().isH5platform check
Changed
- Animation effective values now unified at the router layer -
meta.animationis only injected into navigation options whenAnimationPluginis registered; without registration it has no effect even when configured.navigate.tsno longer falls back to readingmeta.animationinternally, consistent with thePLUGIN_REQUIREDgating for passinganimationat call time - Unified animation platform capabilities - App uses native window animations (
animationType), H5 uses CSS transitions forpush/back, and mini-program is controlled by the host
Refactored
- Extracted helper modules such as
navigation/helpers/uni-api.ts,plugins/animation/helpers,plugins/interceptor/helpers/parse.tsto consolidate the uni navigation calls and platform detection logic
[2.6.0] - 2026-08-27
Added
- Global back guard
onBeforeBack- Newrouter.onBeforeBack()method that intercepts back operations (App physical back key / top navigation bar back /uni.navigateBack, H5 browser back button / back gesture)- Returning
falseblocks the back,true/undefinedallows it; supports async (Promise) and is not limited by uni-app's synchronousonBackPressreturn - On App, wired via the global mixin's
onBackPressto the physical back key / nav bar back /navigateBack; after the guard passes it returns manually, using internal flags to avoid recursion - On H5, wired to browser back via the
popstateevent, using the "undo the back → run the guard chain → re-run the back after the guard passes" strategy - After the guard passes, it reuses the
beforeEach→beforeResolveguard chain; abort / redirect behavior matches full navigation - New
BackGuard/BackGuardReturntypes - Platform limits: App / H5 can be intercepted; iOS swipe-back requires
app.setSideSlipGestureto disable the gesture; native mini-program back cannot be intercepted
- Returning
- iOS swipe-back gesture control (
app.setSideSlipGesture) - NewRouterOptions.appApp-platform-specific config that dynamically sets the iOS swipe-back gesture per current route (maps toplus.webview.setStyle({ popGesture }))'none'disables swipe-back so it flows through the guard chain (onBeforeBacktakes effect)'close'enables the native swipe-back gesture, preserving the native gesture experience (swipe bypasses the guard)- Called automatically by the global mixin on page
onShow, effective only on iOS - New
AppRouterOptions/SideSlipGesturetypes
getPlatform()platform detection utility - Unified platform detection entry, based onuni.getSystemInfoSync()with caching- Returns
PlatformInfo:isApp/isH5/isMp/isIOS/isAndroid/uniPlatform/osName - Backward compatible: when
uniPlatformis missing, falls back totypeof plus/typeof windowto infer App / H5 - New
plusglobal object anduni.getSystemInfoSync()type declarations
- Returns
Changed
- Unified platform detection - InterceptorPlugin's
isWebPlatform()now usesgetPlatform().isH5, removing scatteredtypeof window/typeof documentspecial checks
[2.5.0] - 2026-08-23
Added
- RouterLink renders as a native
<a>tag on H5 - Restores the native capabilities of browser links (semantics, right-click new tab, URL recognition, accessibility, nativehrefbehavior)- On H5 it renders as
<a :href>via#ifdef H5conditional compilation, withhrefprovided reactively byuseLink; a normal left-click callspreventDefaultand defers to router navigation, with the guard chain still applied - Modifier keys (Ctrl/Cmd/Shift/Alt) or a middle-click preserve native browser behavior (e.g., open in a new tab)
hrefautomatically adapts to hash routing (#prefix), ensuring right-click "open in new tab" routes correctly- Other platforms (App / mini-program) render as
<navigator>(uni-app native navigation component), behavior unchanged - Modifier-key detection and hash-prefix logic in the script use
#ifdef H5conditional compilation, stripped at compile time on non-H5 platforms to avoid the navigation being wrongly intercepted due to non-H5 event objects lacking thebuttonproperty
- On H5 it renders as
[2.4.0] - 2026-08-21
Added
- Controllable Redirect - Completes the guard return-value mode with redirect-method control, allowing explicit specification of the navigation method used for a redirect by returning a
{ location, mode }object- New
NavigationRedirectinterface extendingNavigationGuardReturn(adding a| NavigationRedirectbranch) modesupports'push'(uni.navigateTo) /'replace'(uni.redirectTo) /'relaunch'(uni.reLaunch)- Redirect method priority: explicit
mode> original navigation method >backfalls back torelaunch - When
modeis omitted, behavior is unchanged (uses the original navigation method), fully backward compatible guardRoute()cold-start flows also support controllable redirects- Example:
- New
router.beforeEach((to, from) => {
if (to.meta.requireAuth && !isLoggedIn()) {
// Use replace to go to the login page, avoiding the login page lingering in the page stack
return { location: { name: 'login', query: { redirect: to.fullPath } }, mode: 'replace' }
}
})Fixed
- Double
?when injecting internal keys into string paths containing a query -injectQueryKeydid not split the existing query when injecting__nav_id/__params_keyinto a string path already containing a query (e.g.,'/detail?id=1'), producing a malformed URL like?id=1?__nav_id=...- Fix: string paths are first split on
?into path + existing query, then merged and injected, resulting in?id=1&__nav_id=... - This also benefits ChannelPlugin (
__nav_id) and ParamsPlugin (__params_key)
- Fix: string paths are first split on
[2.3.1] - 2026-08-21
Fixed
- RouterLink console error on H5 - Replaced the root element from
<navigator>to<view>, fixing the uni-h5 console error[ERROR] <navigator/> should have url attributefired on every click on H5<view>also supports press-state properties likehover-class/hover-stop-propagation/hover-start-time/hover-stay-time- Actual navigation is fully driven by
@click.stop="handleClick"calling the router API, so navigation functionality is unaffected
Changed
- Component emits type refactor - Converted
RouterLinkEmitsandTabBarEmitsfrominterfacetotypealiases, consistent with the style of other type definitions
[2.3.0] - 2026-08-19
Added
useLinkcomposable API - Exposes RouterLink's internal behavior as a composable function for building custom navigation components- Behavior matches Vue Router 4.x's
useLink, returning reactive route info, match state, and a navigation method - Returns:
route(resolved route),href(target path),isActive(is it a match),isExactActive(is it an exact match),navigate(performs navigation) - Example:
- Behavior matches Vue Router 4.x's
import { useLink } from '@meng-xi/uni-router'
const { href, isActive, navigate } = useLink({
to: { name: 'pagesDetailDetail', query: { id: '1' } }
})
// Reactive binding
const classes = computed(() => ({
'nav-link': true,
'nav-link-active': isActive.value
}))isNavigationFailureutility function - Navigation failure type-checking helper, replacing manualinstanceof+codechecks
import { isNavigationFailure, RouterErrorCode } from '@meng-xi/uni-router'
try {
await router.push('/somewhere')
} catch (error) {
if (isNavigationFailure(error, RouterErrorCode.NAVIGATION_DUPLICATED)) {
// Ignore duplicated navigation
}
}UseLinkOptions/UseLinkReturntypes - Option and return types foruseLink
[2.2.0] - 2026-08-18
Breaking Changes
next()callback mode fully removed - The guard system now fully adopts the return-value mode, fully consistent with Vue Router 4.x- Removed the
NavigationGuardNexttype; the(to, from, next)three-argument signature is no longer supported - Removed the
NavigationGuardNextOptionstype;next(location, { mode })is no longer available - Removed the
runGuardWithNext()function and the entirenextcallback execution path - Removed the
runGuard()mode-detection dispatcher, replaced by arunGuard()that only supports return-value mode - The
NavigationGuardtype signature changed from(to, from, next?)to(to, from) - Guards control navigation behavior solely through their return value:
return undefined/return true→ allowreturn false→ abort navigation (NAVIGATION_ABORTED)return '/login'/return { name: 'login' }→ redirectreturn new Error()/throw new Error()→ cancel navigation (NAVIGATION_CANCELLED)
- Removed the
Added
onBeforeRouteLeavecomposable API - An in-component leave guard that controls the leave navigation via its return value and is automatically removed when the component unmounts- Behavior matches Vue Router 4.x's
onBeforeRouteLeave, supporting both sync and async guards - Example:
- Behavior matches Vue Router 4.x's
import { onBeforeRouteLeave } from '@meng-xi/uni-router'
// Sync leave confirmation
onBeforeRouteLeave(() => {
if (hasUnsavedChanges) {
return false
}
})
// Async confirmation dialog
onBeforeRouteLeave(() => {
if (hasUnsavedChanges) {
return new Promise(resolve => {
uni.showModal({
title: '确认离开',
content: '有未保存的修改,确定要离开吗?',
success: res => resolve(res.confirm)
})
})
}
})RouteLeaveGuardtype - In-component leave guard function type, with the same return values asNavigationGuard
Important Limitation
onBeforeRouteLeave can only intercept navigation that goes through the router (push / replace / back / relaunch); it cannot intercept the physical back key, swipe-back gesture, browser back button, or the mini-program top-left back button.
Migration Guide
- Rewrite
(to, from, next) => { next() }as(to, from) => { return } next(false)→return falsenext({ name: 'login' })→return { name: 'login' }next({ name: 'login' }, { mode: 'replace' })→return { name: 'login' }(modeis no longer supported; the redirect reuses the original navigation method)
[2.1.0] - 2026-08-16
Added
- Guard return-value mode (Vue Router 4.x compatible) - Guards now fully support controlling navigation behavior through their return value, without calling the
next()callbackreturn undefined/return true— allowreturn false— abort navigation (NAVIGATION_ABORTED)return RouteLocationRaw— redirectreturn Error/throw Error— cancel navigation (NAVIGATION_CANCELLED)return { location, mode }— redirect + specify navigation method
NavigationGuardReturntype - Guard return-value type supportingvoid | undefined | boolean | RouteLocationRaw | Error | nullafterEachaccepts afailureargument - The third argumentfailureof the after hook is passed on failed navigation, letting you distinguish successful/failed navigation
Changed
- Automatic guard mode detection - Detects the mode by the number of function parameters: three params
(to, from, next)→ callback mode (backward compatible), two params(to, from)→ return-value mode (recommended) - Mixing warning - A console warning is shown when both the
next()callback and a return value are used
Compatibility
- The
next()callback mode remains fully compatible and is marked deprecated - Legacy guard code continues to work without modification
[2.0.0] - 2026-07-13
Added
- Plugin architecture - Core functionality is split into plugins registered on demand; unregistered plugins add no bundle size or runtime overhead
RouterPlugininterface - a Swiper.js-style plugin system that registers hooks viainstall(context, options)PluginContextinterface - the hook-registration API the router exposes to plugins, supporting 7 lifecycle hooksRouterOptions.plugins- plugin registration config; passing an array of plugins enables the corresponding featuresPLUGIN_REQUIREDerror code - thrown when using a feature whose plugin is not registered, helping quickly locate issues
- ParamsPlugin - Page parameter passing plugin (split out of the core)
push/replace/relaunchsupportparamsto pass complex data without exposing it in the URL- Persistent storage of params via
persistent; still readable after an H5 refresh RouterOptions.paramsPersistentglobal default
- ChannelPlugin - Inter-page communication plugin (split out of the core and enhanced)
useUniEventChanneloption - when enabled, all navigation methods (push/replace/relaunch) supporteventChannelUniEventChannelclass - implemented on theuni.$emit/$on/$off/$onceglobal event bus, replacing the native EventChannel that was only available for push- Sticky event caching -
emit()always caches event args, andon()/once()asynchronously fire cached events on registration, resolving timing races usePageChannel()composable API - a convenient way for the target page to obtain the communication channelnoopChannelexport - an empty-operation channel returned when there is no__navId, avoiding null pointers
- InterceptorPlugin - uni API interception plugin (split out of the core)
- The
RouterOptions.interceptUniApioption requires this plugin to take effect - Intercepts
navigateTo/redirectTo/switchTab/reLaunch/navigateBackto unify the guard flow
- The
- AnimationPlugin - Navigation animation plugin (split out of the core)
push/replace/backsupport animation arguments, effective only on App- Route-level
meta.animationdefault animation config
applySyncHooksnavigation preprocessing - RunsrouteSyncHooksbeforesetCurrentRoute, extracting internal keys like__nav_idfrom query into params, sousePageChannel()can correctly obtain the channel during the target page'sonLoad/<script setup>
Changed
syncRoutede-duplication optimization - RunsrunSyncHooksbefore comparing to remove internal keys (e.g.,__nav_id,__params_key) from the URL query, avoiding an extraonRouteChangefiring on everyonShowdue to internal-key differences- Centralized route-location parsing - Logic such as
resolveLocation/extractParamsKeyinrouter/index.tswas extracted toutils/route.ts, removing duplication withrouter/location.ts - Data sharing between plugins -
pluginData: Record<string, any>is passed between stages of the navigation flow; plugins read/write data via agreed keys, avoiding direct coupling
Breaking Changes
createRoutermust explicitly register plugins -params/events/animation/interceptUniApifeatures are no longer available by default; you must register the corresponding plugins in thepluginsarray
// 1.x - features available by default
const router = createRouter({ routes, interceptUniApi: true })
// 2.0 - plugins must be registered explicitly
const router = createRouter({
routes,
plugins: [ParamsPlugin, ChannelPlugin, InterceptorPlugin, AnimationPlugin],
interceptUniApi: true
})@meng-xi/uni-router/pluginssubpath export - Plugins can be imported from both the main entry@meng-xi/uni-routerand the subpath@meng-xi/uni-router/plugins- Unregistered-plugin features throw
PLUGIN_REQUIRED- Usingparamswithout ParamsPlugin,eventswithout ChannelPlugin,animationwithout AnimationPlugin, or settinginterceptUniApi: truewithout InterceptorPlugin all throw aPLUGIN_REQUIREDerror
Migration Guide
- Add a
pluginsarray increateRouterand register the feature plugins you need - Import plugins from
@meng-xi/uni-router:import { ParamsPlugin, ChannelPlugin, InterceptorPlugin, AnimationPlugin } from '@meng-xi/uni-router' - Import
usePageChannel()from the@meng-xi/uni-routermain entry - For the uni_modules version, import from
./uni_modules/mxuni-router-v2/js_sdk/index.js
[1.11.0] - 2026-07-10
Added
- TabBar / TabBarItem components - Custom bottom navigation bar, to be used together
- TabBar Props:
color/selectedColor/bgColor/borderStyle/fixed/border/placeholder/safeAreaInsetBottom/zIndex/beforeChange - TabBar Events:
change(item, index)/error(error) - TabBarItem Props:
to/text/iconPath/selectedIconPath/dot/badge/badgeMax/badgeColor/replace - TabBarItem Slots:
#icon="{ active }"custom icon,defaultcustom text - Built-in badge system:
dotsmall dot (higher priority than badge),badgenumeric/text badge,badgeMaxcap,badgeColorcustom color beforeChangeinterceptor: returningfalseor rejecting prevents the switch; supports async
- TabBar Props:
- SCSS theming - Component styles migrated to SCSS, supporting two-level overrides
- SCSS variable
!default: compile-time override (via vitecss.preprocessorOptions.scss.additionalData) - CSS custom properties: runtime override (set
--mx-tabbar-*/--mx-tabbar-item-*on the parent element) - TabBar variables:
--mx-tabbar-height/--mx-tabbar-background/--mx-tabbar-border-color - TabBarItem variables:
--mx-tabbar-item-icon-size/--mx-tabbar-item-font-size/--mx-tabbar-item-gap/--mx-tabbar-badge-color/--mx-tabbar-badge-dot-size/--mx-tabbar-badge-font-size/--mx-tabbar-badge-min-width/--mx-tabbar-badge-line-height/--mx-tabbar-badge-padding
- SCSS variable
TabBarItemPropstype export - New export from the@meng-xi/uni-routermain entry for typing the TabBarchangeevent callback
Changed
- Component directories restructured to the easycom convention - Components changed from flat files to the
components/<name>/<name>.vuenested structure, conforming to easycom auto-registrationcomponents/RouterLink.vue→components/router-link/router-link.vuecomponents/TabBar.vue→components/tab-bar/tab-bar.vuecomponents/TabBarItem.vue→components/tab-bar-item/tab-bar-item.vue- Shared context
tabbar-context.ts→tab-bar/context.ts - Component types extracted to a sibling
type.ts(router-link/type.ts,tab-bar/type.ts)
- uni_modules version imports local js_sdk - Component imports inside the mxuni-router package changed from
@meng-xi/uni-routerto the relative path../../js_sdk/index, removing the runtime dependency on the npm package - uni_modules version tag name change -
<mxuni-router>changed to<RouterLink>(easycom auto-registration) - Component TypeScript type extraction - Each component's props/emits types extracted to a sibling
type.ts; shared context (InjectionKey + interface) placed incontext.ts - Component CSS → SCSS - RouterLink, TabBar, and TabBarItem styles all migrated to SCSS using variables and custom properties
Migration Notes
npm users need to update their component import paths:
| Old path | New path |
|---|---|
@meng-xi/uni-router/components/RouterLink.vue | @meng-xi/uni-router/components/router-link/router-link.vue |
@meng-xi/uni-router/components/TabBar.vue | @meng-xi/uni-router/components/tab-bar/tab-bar.vue |
@meng-xi/uni-router/components/TabBarItem.vue | @meng-xi/uni-router/components/tab-bar-item/tab-bar-item.vue |
uni_modules users need no changes; easycom auto-registers <RouterLink> / <TabBar> / <TabBarItem>.
[1.10.0] - 2026-07-09
Added
- Built-in inter-page communication manager - New
useUniEventChanneloption andUniEventChannelclass, implemented on theuni.$emit/$on/$off/$onceglobal event bus, replacing the nativeuni.navigateToEventChannel so all navigation methods (push/replace/relaunch) support bidirectional inter-page communicationRouterOptions.useUniEventChannel?: boolean(defaultfalse) - when enabled, all navigation methods use the built-in communication manager; when defaultfalse, onlypushuses the nativeuni.navigateToEventChannel and other methods do not support page communicationUniEventChannelclass - implements theEventChannelinterface withemit/on/once/offmethods; each navigation generates a uniquenavigationId(formatnav-<timestamp>-<seq>), wrapped bywrapEventName()asuni-router:{navId}:{eventName}to isolate event channels and avoid cross-talk between navigations__nav_idis passed through the URL query; the target page reads and rebuilds the channel onsyncCurrentRoute, so communication can be restored after an H5 refresh- New
noopChannelexport - an empty-operation channel whose methods are all no-ops that return itself;usePageChannel()returnsnoopChannelwhen there is no__navId, avoiding null pointers
- Sticky event caching -
emit()always caches event args topendingEvents;on()/once()asynchronously fire already-cached events on listener registration (without deleting the cache), resolving the timing race between the sender'semitand the target page'ssetuplistener registration- Applicable scenario: after navigating, the sending page immediately
emits; the target page'sonlistener insetupstill receives the cached event - The cache is cleaned up with
UniEventChannel.destroy()(called automatically on pageonUnmounted)
- Applicable scenario: after navigating, the sending page immediately
usePageChannel()composable API - A convenient way for the target page to obtain the communication channel- Reads
route.params.__navIdand returns the correspondingUniEventChannelinstance; returnsnoopChannelwhen there is no__navId - Calls
destroyChannel(navId)automatically ononUnmounted()to clean up listeners and cache, avoiding memory leaks
- Reads
NavigationResultreturn type - Thepush/replace/relaunchreturn value was extended fromRouteLocationtoNavigationResult(inheritsRouteLocation, adds optionaleventChannel?: EventChannel)- Default mode:
eventChannelis available only forpush(corresponding touni.navigateTo) useUniEventChannel: true: all navigation methods return the built-inUniEventChannel- Type backward compatible:
NavigationResult extends RouteLocation, so the originalconst route: RouteLocation = await router.push(...)still works
- Default mode:
- Channel registry (internal) -
registerChannel/getOrCreateChannel/getRegisteredChannel/hasChannel/destroyChannelmanage thenavId → UniEventChannelmappingregisterChanneluses a first-wins strategy: returns false if a channel for the samenavIdalready exists, avoiding duplicate registrationgetOrCreateChannelreuses a registered channel first, creating a new one otherwise
- RouterLink's
navigatedevent supports all navigation methods - With theNavigationResultreturn type,navigate()now fires thenavigatedevent uniformly for push/replace/relaunch and passeseventChannel(only push has a value in default mode; all methods have a value whenuseUniEventChannel: true); in 1.9.0 replace/relaunch had noeventChanneland only push fired it, which was the consistent behavior at the time
Changed
- Improved JSDoc for RouterLink's
eventsprop andnavigatedevent - Clarifies that in default modeeventsonly works forpushandnavigated'seventChannelonly has a value forpush; after enablinguseUniEventChannel, all navigation methods are affected
[1.9.0] - 2026-07-06
Added
- Global mixin auto-sync of route state -
install()registersapp.mixin({ onShow() { router.syncRoute() } }), so every page auto-syncs route state ononShowwithout manually callingsyncRoute()in each page- The mixin hook runs before the component's own
onShow; combined withsyncRoute()'s de-duplication (skips when path + query are identical), redundant syncs are avoided - When the app returns from background, the active page's
onShowauto-triggers sync; no manual call is needed inApp.vue'sonShow onLoadprecedesonShow; callsyncRoute()manually if you need to read route info inonLoad
- The mixin hook runs before the component's own
Fixed
- params lost after
back()- Duringpush/replace, the actual navigation URL preserves__params_key(not visible inroute.query); afterback()returns to the original page,syncCurrentRoutereads the key from the URL and rebuilds params withpeek- Issue:
matcher.resolveremoves__params_keyfrom the query, leaving the actual navigation URL without the key, so params could not be rebuilt from the URL afterback() - Fix:
performNavigationextracts the key viaextractParamsKeyafter resolving, andexecuteNavigationstitches the key back into the query of the actual navigation URL;syncCurrentRoutereads the key from the URL and rebuilds params withpeek(notget) to avoid lazy cleanup deleting by mistake
- Issue:
setCurrentRoutetiming -setCurrentRoute(to)was moved to before the uni navigation API call, ensuringroute.valueis already the complete target route (includingname/params) when the target page'sonLoad/onShowrun- Issue: previously
setCurrentRouteran after the uni API succeeded, so when the target page'sonLoad/onShowfired,currentRoutewas still the source route androute.valuelacked the target route info - Fix: call
setCurrentRoute(to)before callingnavigateTo/replaceTo/relaunchTo; roll back tofromif the navigation API fails
- Issue: previously
[1.8.1] - 2026-06-26
Fixed
interfaceobjects cannot be assigned to theparamsfield - Fixed the type error whenrouter.push({ params })receives an object defined withinterfacein v1.8.0- Issue: In v1.8.0, the types of
RouteLocationPathRaw.params/RouteLocationNamedRaw.paramswereinterface ParamObject(with index signature{ [key: string]: ParamValue }). Under strict TypeScript,interface-defined object types have no explicit index signature and cannot be assigned to index-signature types, soconst params: MyInterface = {...}; router.push({ params })failed with "Index signature for type 'string' is missing" - Fix: Added a
ParamsInputtype (object) as the input-side type; theparamsfield now usesParamsInput, which structurally subtypes anyinterfaceobject. On the output side,ParamObjectchanged frominterfaceto atypealias (Record<string, ParamValue>), preserving index-signature access - Design note: Research on vue-router's
RouteParamsRawGeneric(Record<string, RouteParamValueRaw | ...[]>) found that its value type only contains primitives (string | number | null | undefined), andinterfaceobjects with primitive-typed properties structurally subtypeRecord. But mxuni-router'sParamValueincludesobject/ParamValue[]branches (for complex data). In that case,Record<string, ParamValue>is still incompatible withinterfaceobjects under strict vue-tsc, soobjectis required - JSON serializability is validated at runtime by
ParamsManager - New
ParamsInputtype export
- Issue: In v1.8.0, the types of
[1.8.0] - 2026-06-25
Added
- Cold-start guard check
guardRoute()- Solves the problem where, when a user enters a page directly via an H5 URL / mini-program scene value / App deeplink, the page is loaded directly by the uni-app framework without going through router navigation, so guards (beforeEach, etc.) do not runRouter.guardRoute(location?, options?)- Runs the guard chain check for a given route (without actually navigating) and decides whether to redirect based on the guard resultGuardRouteOptions- option type including anonAbortcallback fired with aNavigationFailurewhen the guard aborts- Behavior: guard passes → no navigation, resolves the target route; guard redirects → navigates using the guard-specified method (default
relaunch, clearing the stack to avoid returning to a protected page); guard aborts → calls theonAbortcallback and rejects withNavigationFailure - Runs the full guard chain:
beforeEach→beforeEnter→beforeResolve - Typical usage: in
App.vue'sonLaunch, callrouter.isReady().then(() => router.guardRoute(undefined, { onAbort: () => router.relaunch('/pages/index/index') }))
UniApiError/UniApiCausetype exports - Exported the previously internal uni API error types to improve the type readability ofNavigationFailure.causeUniApiCause- the error-reason type of the uni navigation APIfailcallback ({ errMsg: string })UniApiError- interface containingapi(the failed API name, e.g.,navigateTo) andcause(the original error reason)NavigationFailure.causenarrowed fromunknowntoUniApiError, present only onNAVIGATION_API_ERRORisUniApiError()changed to a type guard (error is UniApiError) for narrowing afterinstanceof
Changed
ParamValuetype compatibility enhancement - The object branch changed from recursiveParamObjecttoobject, compatible withinterface-defined object types (which lack an index signature and cannot be assigned to{ [key: string]: ... }); added anundefinedbranch to support objects with optional properties (JSON.stringifyignoresundefinedproperties)RouterLinkcomponent refactor - Location computation logic extracted into acomputed; when no extra options (animation/events/persistent) are passed,tois used directly, avoiding needless object wrapping- Tightened uni API
failcallback types - Inenv.d.ts, thefailcallback parameter type of each navigation API (navigateTo/redirectTo/switchTab/reLaunch/navigateBack) was narrowed fromunknowntoUniApiCause
Fixed
- Guard mixing-mode warning - Outputs a warning when a guard calls both
next()and returns a Promise (async errors afternext()are silently swallowed; developers should pick one resolution mode: thenext()callback orasync/await, not both) syncCurrentRouteparam cleanup - Removed the unused_fromparameter insidesyncRoute()
[1.7.0] - 2026-06-25
Added
- Controllable guard redirect method - The
next()callback gained an optionaloptionsargument supporting a specific navigation method for guard redirectsNavigationGuardNextOptions- the optional argument type of thenext()callback containing amodefieldNavigationRedirectMode- the redirect-method type ('push' | 'replace' | 'relaunch')next(location, { mode })- specifies usingpush/replace/relaunchon redirect- When
modeis not specified, it reuses the original navigation method that triggered the guard (backward compatible) - When the original navigation is
back, omittingmodefalls back torelaunch(becausebackcannot jump to a target outside the page stack)
Fixed
- H5
interceptUniApicauses TabBar clicks to freeze - 1.6.3 restored switchTab going through the guard chain by reordering execution, but synchronously blockinguni.switchTabon H5 still leaves the TabBar component's internal "switching" state uncleared, so subsequent clicks are ignored. Now, for H5, switchTab uses the "let the original call through + sync state in the success callback" strategy- New
isWebPlatform()to detect the H5 platform (via the presence ofwindow/document) - New
handleWebSwitchTab()wraps thesuccesscallback to callrouter.syncRoute()after switchTab completes - Trade-off: external
uni.switchTabcalls on H5 no longer pass through the pre-guard; TabBar page permission control must be handled in the pageonShowlifecycle - Mini-program and App platforms are unaffected and still use the full "block + forward" flow
- New
[1.6.3] - 2026-06-24
Fixed
interceptUniApimakes the H5 TabBar unclickable - In the interceptor'sinvokehook,args.url = ''ran beforehandleInterceptedNavigation(), causingparseUniUrl('')to return an empty path and theswitchTabnavigation to be swallowed. On H5, the TabBar is a Vue component and callsuni.switchTab, which after triggering the interceptor left the URL cleared prematurely; on mini-program the TabBar is a native component and clicks do not go throughuni.switchTab, so it was unaffected. Execution order was swapped: parse the URL and trigger router navigation first, then clear the URL as a safety net, while restoring switchTab to go through the guard chain
[1.6.2] - 2026-06-23
Fixed
isReady()timing fix -markReady()moved fromsetCurrentRoute()intoinstall(), ensuringisReady()callbacks run after all plugins (e.g., Pinia) are installed, rather than firing immediately whencreateRouter()constructs
[1.6.1] - 2026-06-23
Changed
isSameQueryempty-object fast path - Added reference equality (a === b) and double-empty-object (keysA.length === 0) quick returns, avoiding unnecessaryObject.keysandeveryoverhead in high-frequency scenarios- Centralized
Object.freezelogic - The freeze logic formeta,query, andparamswas consolidated fromsetCurrentRouteandcreateStartLocationinto thecreateRouteLocationfactory, removing duplicated code so future conditional freezing needs only one change
[1.6.0] - 2026-06-23
Added
- Page parameter passing (params) -
push/replace/relaunchsupportparamsfor passing complex data (objects, arrays, etc.) without exposing it in the URL; the target page reads it viaroute.paramsRouteLocationPathRaw.params/RouteLocationNamedRaw.params- page params passed during navigation, supporting JSON-serializable dataRouteLocation.params- the resolved route location gained aparamsfield (Readonly<ParamObject>) readable directly by the target pageParamObject/ParamValuetypes - page param type definitions supporting nested objects and arraysQueryValuetype - query param value type (string | number | boolean) for the input type of thequeryfield
- Persistent param storage - the
persistentoption persists params touni.setStorageSync, still readable after an H5 refreshRouteLocationPathRaw.persistent/RouteLocationNamedRaw.persistent- specifies whether a single navigation is persistentRouterOptions.paramsPersistent- global default; whentrueall params are persistent by default, with single-navigationpersistentable to override
- Enhanced query-param methods -
RouteLocationprovides three convenience methods that automatically parse query params to a specified typequeryInt(key, defaultValue?)- parses a query param as an integer, returningdefaultValueon failurequeryNumber(key, defaultValue?)- parses a query param as a number (supports floats), returningdefaultValueon failurequeryBool(key, defaultValue?)- parses a query param as a boolean ('true'/'1'→true,'false'/'0'→false), returningdefaultValuewhen unrecognizable
- RouterLink
paramsprop - declarative navigation supports passing page params, corresponding topush'sparamsoption - RouterLink
persistentprop - declarative navigation supports param persistence, corresponding topush'spersistentoption
[1.5.0] - 2026-06-18
Added
- Router ready-timeout protection - the
readyTimeoutconfig option to preventisReady()'s Promise from hanging forever if router initialization failsRouterOptions.readyTimeout- router ready timeout in milliseconds, default0(never times out); when set above 0,isReady()rejects after the timeoutrouter.isReady()timeout rejection - whenreadyTimeout > 0and the router does not finish initializing within the deadline,await router.isReady()throws a timeout error
Fixed
interceptUniApiinterceptor list doc omittedreLaunch- The v1.0.0 docs only listed four APIs (navigateTo/redirectTo/switchTab/navigateBack), while the actual implementation (including the v1.3.0 addition) intercepts five;reLaunchwas added to the docsRouteMetaindex-signature type fix -[key: string]changed fromunknowntoany, consistent with the actual implementationrouter.back()return value doc fix - Return type corrected fromPromise<void>toPromise<RouteLocation>, consistent with the actual implementation
[1.4.0] - 2026-06-14
Added
- EventChannel inter-page communication -
pushsupports theeventsargument and aneventChannelreturn value for bidirectional inter-page communicationRouteLocationPathRaw.events/RouteLocationNamedRaw.events- event listeners passed during navigation that listen for events the target page sends viaeventChannel.emitNavigationResult.eventChannel- thepushresult gained aneventChannelfield for sending events to the target pageEventChannelinterface - fullon/once/off/emitmethod definitionsEventListenerstype - the event-listener map type- Passing
eventsin non-push modes (replace / relaunch) outputs a warning and ignores it - TabBar pages (switchTab) do not support
events; passing them outputs a warning and ignores it
- RouterLink
eventsprop - declarative navigation supports inter-page communication, corresponding touni.navigateTo'seventsargument - RouterLink
@navigatedevent - fires after successful navigation with the argumentEventChannel | undefined; only push mode returns aneventChannelinstance - uni API interceptor supports
events- When interceptinguni.navigateTo, theeventsargument is extracted and forwarded to the router - Type exports - New
EventChannelandEventListenerstype exports
[1.3.0] - 2026-06-12
Added
- relaunch navigation method -
router.relaunch(location)closes all pages and opens the target page, corresponding touni.reLaunch- TabBar pages automatically switch to
uni.switchTab uni.reLaunchdoes not support animation arguments; passing them outputs a warning- No duplicate-navigation detection (in a stack-clearing scenario the target page may be the current page)
- Runs the full guard chain (beforeEach → beforeEnter → beforeResolve → afterEach)
- TabBar pages automatically switch to
- RouterLink
relaunchprop - declarative navigation supports relaunch mode, with higher priority thanreplace - uni API interceptor adds
reLaunch- interceptsuni.reLaunchcalls and forwards them torouter.relaunch()
[1.2.0] - 2026-06-11
Added
- Navigation animations - Complete page-transition animation support, effective only on App and auto-ignored on other platforms
NavigationAnimationinterface - animation config type containingtypeand optionaldurationUniAnimationTypetype - covers all animation types uni-app supports (slide-in/out, fade-in/out, zoom-in/out, pop-in/out, auto, none)DEFAULT_ANIMATION_DURATIONconstant - default animation duration 300msRouteLocationPathRaw.animation/RouteLocationNamedRaw.animation- animation arguments passed at navigation time, overridingmeta.animationRouteMeta.animation- route-level default animation configback(delta?, animation?)-back()gained an optionalanimationargument- RouterLink gained an
animationprop for declarative navigation animations - Animation priority:
passed at call time>meta.animation>uni default
[1.1.2] - 2026-06-10
Fixed
getCurrentPages()environment protection - NewsafeGetCurrentPages()function that returns an empty array whengetCurrentPagesdoes not exist in SSR / Node environments, avoiding aReferenceError- Interceptor
invokelow-version base-library compatibility - Before intercepting an external navigation call,args.urlis set to an empty string to prevent low-version mini-program base libraries from ignoring thefalsereturn value and running the original API anyway - Interceptor duplicate-installation warning -
installInterceptorsoutputsconsole.warnwhen it detects an existing active manager, reminding that only a single router instance is supported
[1.1.1] - 2026-06-09
Fixed
back()did not fire theafterEachguard -router.back()did not run theafterEachhook after navigation completed; fixedback()guard-mode error - The guard mode forback()navigation was corrected from'push'to'back', ensuring the guard chain correctly recognizes the back navigationsyncRoute()ignored query changes -syncRoute()only compared paths, not query params, so route state was not synced when query changed; it now compares both path and queryapp.onUnmountcompatibility - Callingapp.onUnmountdirectly ininstallerrors in uni-app environments (the API is new in Vue 3.5+); a defensive check was added
[1.1.0] - 2026-06-08
Added
- Guard timeout protection - the
guardTimeoutconfig option; navigation is auto-aborted when a guard does not callnext()within the deadline, default 10000ms, set to 0 to disable - Route-change listener -
router.onRouteChange()registers a route-state-change listener fired on navigation completion and state sync, returning a function to remove the listener - Route-state sync marker - the
RouteLocation.syncedfield marking whether a route change was triggered by state sync (e.g., the physical back key) - RouterLink error event - the
<mxuni-router>component gained an@errorevent firing on navigation failure, passing aNavigationFailureobject
Changed
- Enhanced uni API interception - Optimized the
interceptUniApiinterceptor logic for greater interception stability - Enhanced guard execution - Optimized the guard-chain execution logic with timeout protection and error handling
- Enhanced composable API - Optimized the internal implementation of
useRouter()/useRoute() - fullPath determinism -
buildFullPathsorts query param keys so the same query produces a consistentfullPath - install type fix -
install(app)parameter type changed fromunknowntoAppfor better type hints
[1.0.0] - 2026-06-07
Added
- Router core -
createRouter()creates a router instance supporting theroutes,strict, andinterceptUniApioptions - Route navigation -
router.push()navigates to a new page,router.replace()replaces the current page,router.back()goes back to the previous page - Named routes - Navigate via the
namefield without hardcoding path strings - Route meta info - the
metafield supportstitle,isTab,requireAuth, and custom extension fields - Global before guards -
router.beforeEach()runs before every navigation, supporting abort, allow, and redirect - Global resolve guards -
router.beforeResolve()runs after all before guards and route-scoped guards complete - Global after hooks -
router.afterEach()runs after navigation completes - Route-scoped guards - the
beforeEnteroption fires when entering a specific route - Guard redirects - calling
next(location)in a guard redirects to another route, supporting multi-level redirects (max depth 10) - Composable API -
useRouter()gets the router instance,useRoute()gets the current route location - Error handling - the
RouterErrorroute error class and theNavigationFailurenavigation failure class (containingto,from,causeinfo) - Global error capture -
router.onError()registers error-handling callbacks - Route overview -
router.resolve()resolves a route location (without navigating),router.getRoutes()gets all route configs,router.hasRoute()checks whether a route exists - TypeScript type hints - the
RouteNameMapinterface supports module augmentation for autocomplete and type-checking of route names and paths - uni API interception - the
interceptUniApioption interceptsuni.navigateTo/uni.redirectTo/uni.switchTab/uni.navigateBackto unify the guard flow - Duplicate-navigation detection -
pushto the current page is auto-rejected with aNAVIGATION_DUPLICATEDerror - Concurrent-navigation queuing - multiple concurrent navigations are queued automatically; the next runs after the previous completes
- Path auto-normalization - paths auto-prepend a leading
/, and query strings auto-parse intoqueryobjects
Error Codes
| Error code | Description |
|---|---|
NAVIGATION_ABORTED | Navigation aborted by a guard |
NAVIGATION_CANCELLED | Navigation cancelled (guard error or redirect limit) |
NAVIGATION_DUPLICATED | Duplicate navigation to the current location |
ROUTE_NOT_FOUND | No matching route found |
NAVIGATION_API_ERROR | uni navigation API call failed |
SETUP_ERROR | Router initialization or usage error |
[0.1.4] - 2025-07-28
- Added the Hooks function
useMxRouter - Added the
Routercomponent push,back, andgoon theRouterclass now support animations on the app platform- Improved md documentation descriptions
[0.1.3] - 2025-07-24
- Added the
umd.jsfile - Renamed the
MxRouterclass to theRouterclass - The
Routerclass supports singleton-style invocation internally - The
Routerclass added thecustomGetCurrentRouteoption and thesetCustomGetCurrentRoutefunction for setting a customgetCurrentRoutefunction
[0.1.1] - 2025-07-20
- Adjusted the directory structure and vite.config configuration; updated the md file content to match the npm package
[0.1.0] - 2025-07-19
Initial release.
@mengxi/uni-routeris a routing library tailored for uni-app that closely mirrors thevue-routerstyle while shipping practical utility functions, helping developers implement multi-platform routing efficiently.
vue-router-like style - Familiar API design that lowers the learning curve and letsvue-routerusers get up to speed quickly- Multiple navigation methods - Supports
push,replace,launch,tab,go,backfor various navigation scenarios - Global guard mechanism - Before guards (auth checks, route interception) and after hooks (logging, page stats)
- Utility functions - Provides
parseLocation,buildUrl,getCurrentRoute, etc. to simplify routing operations - Multi-platform support - Compatible with H5, mini-program, App, and other platforms uni-app supports
See the Releases page for the full history.
