4.4 KiB
Architecture
Ownership boundary
Vue Router owns matching, lazy components, committed routes, redirects, guards, URL serialization, and browser history. Native Vue Router owns a separate visual ledger containing mounted route entries, scroll/focus state, cache status, and the current interactive transaction.
A forward drag calls router.resolve() and Vue Router's public loadRouteLocation(), then renders the location through <RouterView :route> without calling push(). The URL and application history remain unchanged until the gesture commits.
Each preview subtree receives a scoped routeLocationKey, so useRoute() returns preview params even though the global route is not committed. A normal router.push() or replace() runs only after the gesture chooses to commit. A guard failure cancels the transaction and removes the preview.
Preview routes can contain ordinary Vue <Suspense> boundaries. A route may therefore become a live navigation surface immediately, show its fallback while async child setup continues, and preserve the resolved child when the route later moves into the mounted cache. Route guards remain commit-time authority and can still reject that cached destination on a later entry attempt.
Transaction lifecycle
Transactions move through interactive, committing, settling, and cancellation states. They expose normalized progress and velocity plus fromKey, toKey, direction, presentation, and optional source geometry.
The runtime deliberately keeps two ledgers. The navigation stack mirrors committed push/replace/pop semantics and is the only source of predictive-back targets. The view cache owns mounted component instances independently, so a replaced tab can be reused without becoming an accidental back destination.
Pointer movement changes only a CSS progress variable. Built-in presentations restrict active-frame work to compositor-friendly properties. Velocity is expressed as normalized route progress per second, so gesture behavior remains consistent across screen sizes. Release uses distance/velocity intent and a damped spring whose settling rate follows the user's flick speed. Reduced-motion mode settles immediately.
Settling animations are interruptible. A new button navigation or recognized gesture waits only for any in-flight Vue Router guard/history commit, immediately finalizes the old visual transaction, and begins from the newly authoritative route. It never waits for the previous spring to finish. Leading-edge back recognition runs in the navigator capture phase so partially visible component layers cannot steal the physical back edge.
Component gesture owners stop propagation before a containing navigator can claim the same pointer. Navigator gestures wait for horizontal intent, use pointer capture, respect form controls and data-native-gesture="ignore", and preserve normal vertical scrolling through touch-action: pan-y.
Route metadata
interface NativeRouteOptions {
navigator?: string
presentation?: 'push' | 'reveal' | 'slide' | 'fade' | 'modal' | 'sheet' | string
parent?: RouteLocationRaw | ((route) => RouteLocationRaw)
siblingGroup?: string
siblingOrder?: number
siblingHistory?: 'push' | 'replace'
cache?: boolean
gesture?: boolean | 'edge' | 'full'
}
parent supplies a predictive back target when a deep link starts without an in-memory predecessor. Sibling routes replace history by default and use the built-in slide presentation, which moves both pages one-to-one as adjacent surfaces. Direction comes from siblingOrder. Set siblingHistory: 'push' when browser back should visit prior sibling selections.
Custom presentations
nativeRouter.registerPresentation(definePresentation({
name: 'scale-fade',
axis: 'x',
layerStyle({ role, progress }) {
return role === 'to'
? { opacity: progress, transform: `scale(${0.92 + progress * 0.08})` }
: { opacity: 1 - progress * 0.4 }
},
}))
Applications can call beginInteractive(), updateInteractive(), and finishInteractive() to drive the same transaction engine from a bespoke recognizer.
Cache semantics
The active route and recent inactive routes remain mounted. The default limit is eight inactive views per runtime. Older entries keep their route descriptor but are unmounted and lazily restored when revisited. Application data that must survive eviction belongs in an application store.