Native Vue Router
Native Vue Router is a gesture-first navigation runtime for Vue 3 and Vue Router 5. It keeps Vue Router in charge of matching, URLs, guards, and history while rendering live route stacks that can be manipulated interactively.
The repository includes a reusable headless core, a platform-adaptive visual preset, Capacitor and Electron adapters, and one messaging demo delivered as a PWA and through native hosts.
What works
- Interactive edge pop that can be held indefinitely at any progress.
- Ordered horizontal route paging with replace-by-default history.
- Component-originated route dragging with a live target route.
- Interactive push, adjacent-page sibling slide, modal, sheet, fade, and application-defined presentations.
- Concurrent
fromandtoroutes using only public Vue Router 5 APIs. - Guarded commits: previews do not alter the URL, and rejected navigation springs back.
- Cold-start predictive back through declared parent routes.
- Bounded live view caching, nested router views, focus isolation, RTL, and reduced motion.
- PWA, Electron, and Capacitor iOS/Android hosts.
Run it
npm install
npm run dev
The default app is the installable messaging PWA. Other useful commands:
npm run build # packages, declarations, demo, and service worker
npm test # core transaction tests
npm run test:e2e # desktop and mobile Playwright projects
npm run electron # build and launch the Electron host
npm run cap:sync # build and synchronize iOS and Android projects
Native projects live under apps/capacitor/ios and apps/capacitor/android. Open or run them from apps/capacitor with npx cap open ios, npx cap open android, or npx cap run <platform>.
Test the installed iOS PWA
Run the normal npm run dev command, expose its printed network address through an HTTPS URL, and open that URL on the iPhone. The development server includes the PWA service worker and already listens on the local network. Safari still requires a secure context for the service worker; a plain LAN http:// address is not sufficient. Choose Share → Add to Home Screen, then launch NVR Messenger from its Home Screen icon.
The Navigation Lab reports Standalone, ready, and App reserved when the correct environment is active, and shows the exact build ID plus update-check count. Production builds check for updates whenever the app starts, returns to the foreground, regains connectivity, or has been open for a minute; activation reload waits for any live gesture to finish. Open a conversation and drag from the extreme left edge. The “Leading-edge touches claimed” counter should increment while the router renders its live predictive-back view.
For more aggressive lifecycle testing, open You → Runtime stress lab. It is a deeper route that keeps the primary tab bar, opts into push-style sibling history, exposes its mount lifetime, and renders a one-second async child through <Suspense>. Backing out evicts this pushed screen after its exit; browser Forward reconstructs it and shows the fallback again. To exercise a guard against an already-mounted destination, visit Stories, switch to You, enable Block cached Stories re-entry, and try returning to Stories. The guard rejects and evicts the cached view.
An installed web app cannot access WKWebView.allowsBackForwardNavigationGestures. The demo therefore reserves leading-edge touch sequences at the web-content boundary as an iOS standalone-only safeguard. Capacitor remains the deterministic option when native-level gesture suppression is required.
Minimal integration
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import { createNativeRouter } from '@native-vue-router/core'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: Home },
{
path: '/chat/:id',
component: Chat,
meta: {
native: { presentation: 'push', parent: '/', gesture: 'edge' },
},
},
],
})
const nativeRouter = createNativeRouter({ router })
createApp(App).use(router).use(nativeRouter).mount('#app')
<script setup lang="ts">
import { NativeGestureLink, NativeNavigator, NativeRouterView } from '@native-vue-router/core'
</script>
<template>
<NativeNavigator :siblings="['/', '/stories', '/profile']">
<NativeRouterView />
</NativeNavigator>
<NativeGestureLink to="/chat/maya" presentation="reveal">
Drag this row into the chat route
</NativeGestureLink>
</template>
Import @native-vue-router/core/style.css for the built-in presentation layers. The demo imports @native-vue-router/preset-native/style.css as well.
Use nativeRouter.sibling(to) for tab or peer-route navigation. Direction is derived from siblingOrder, repeated navigation to the active route is a no-op, and siblingHistory: 'replace' keeps cached tab views out of the back stack.
Sibling views are lazy rather than pre-mounted: only the initial route exists on startup, and a sibling joins the bounded cache on its first visit or interactive preview. Route metadata accepts cache: false to opt out or cache: 'pin' for views that must survive ordinary trimming. useNativeViewLifecycle(), the onNativeView* hooks, and useNativeViewActiveEffect() let cached screens pause polling, media, or subscriptions while retaining their local UI state.
Call nativeRouter.unload('/some-route') to manually unmount inactive instances of one location while retaining their lightweight history descriptors. The active route and views participating in a transition are protected.
Capture frame pacing on a real device
The Navigation Lab contains an opt-in profiler. Tap Start profiling, leave the lab, reproduce the choppy navigation once or twice, return to the lab, tap Stop, then Share JSON. Installed iOS PWAs use the system share sheet; other browsers download the file. Attach that JSON to a bug report.
The core API is also available directly:
import { createNativeNavigationProfiler } from '@native-vue-router/core'
const profiler = createNativeNavigationProfiler(nativeRouter, {
metadata: { build: import.meta.env.VITE_BUILD_ID },
})
profiler.start()
// Reproduce the navigation issue.
const report = profiler.stop()
const json = profiler.toJSON(report)
No rAF loop or browser performance observer runs before start(), and stop() removes them. Reports contain frame intervals, refresh-rate estimates, per-navigation timing, cold-mount preparation, route loading, cache eviction, visibility changes, and browser-supported Long Task/layout-shift/resource timing. Route params, query values, and application state are omitted.
Packages
@native-vue-router/core— transactions, route ledger, concurrent views, gestures, caching, and public components/composables.@native-vue-router/preset-native— adaptive tab/back controls, safe-area CSS, and platform motion defaults.@native-vue-router/capacitor— hardware back, deep links, pause cancellation, root exit, and haptics.@native-vue-router/electron— Chromium history-gesture suppression and renderer back/forward bridging.
Design and engineering documentation:
- How it works and why the pattern is uncommon
- Engineering challenges, Vue Router limitations, and trade-offs
- Core principles, scalability, and flexibility
- Architecture reference
- Platform integration reference
Support contract
The target is Vue 3.5+ and Vue Router 5. Installed PWAs, current Electron, and Capacitor 8 are first-class. Normal browser tabs remain functional but browsers can reserve edge gestures that page content cannot consistently override.
MIT