Architecture
April 8, 2026 ยท View on GitHub
Overview
vue-page-stack is a Vue 3 plugin that preserves route component instances in stack order so SPA navigation behaves more like a native app.
At runtime, the library has four main layers:
- Plugin installation
- Navigation state tracking
- Page stack decision logic
- Vue renderer adaptation
Runtime Flow
1. Plugin Installation
Entry point: lib/main.js
The plugin install step:
- Validates the passed
router - Creates a per-app navigation state object
- Registers the
VuePageStackcomponent - Provides the navigation state with Vue
provide - Wires browser back/forward detection
- Wraps router navigation methods
The installation is intentionally idempotent for the same app + router pair.
2. Navigation State Tracking
Files:
lib/core/history.jslib/core/navigation.jslib/plugin/eventRegister.js
core/history.js defines the navigation state shape and the injection key.
core/navigation.js owns the rules for updating navigation state. It translates:
router.push()intopushrouter.replace()intoreplacerouter.go(n)intobackorforward- browser history callbacks into
backorforward
plugin/eventRegister.js applies that strategy to a router instance.
The important design choice here is that navigation state is instance-scoped, not module-global.
3. Page Stack Decision Logic
File: lib/components/VuePageStack.js
VuePageStack is responsible for deciding whether the current render should:
- push a fresh page onto the stack
- replace the current stack entry
- restore a cached page on back navigation
- fall back to a fresh render when cache restore is unsafe
The component does not directly own Vue renderer internals anymore. Instead, it consumes a small renderer adapter contract.
Important helper responsibilities:
isCacheableVNode: decide whether the child can participate in stack cachingnormalizeVNodeForCache: clone vnode when Vue has already attached DOM statecreatePageStackState: isolate stack mutation, restoration, and cleanuphandleBackNavigation: run cache-restore logic for back navigationtrimStackFrom: drop entries that are no longer reachable
Navigation Action Map
| Action | Render expectation | Stack effect |
|---|---|---|
push | render a fresh page | push the previous subtree into the stack |
forward | render a fresh page | treat it like a new entry and keep emitting forward |
replace | render a fresh page | overwrite the current stack entry |
back with cache hit | restore a cached page instance | reuse cached vnode and trim popped entries |
back with cache miss | render a fresh page safely | clear unreachable cached entries and continue fresh |
4. Vue Renderer Adaptation
File: lib/runtime/pageStackRenderer.js
This is the only place that depends on Vue internal renderer behavior.
It provides a small adapter used by VuePageStack:
getInnerChildisSuspenseresetShapeFlagreuseCachedVNodeunmountCached
It also installs custom activate / deactivate behavior on the component instance context so cached component instances can move between the real container and an off-screen storage container.
If Vue internal behavior changes in a future version, this file is the first place to inspect.
Directory Map
Source
lib/main.js: stable package entrylib/constants/config.js: shared constantslib/core/history.js: navigation state factory and injection keylib/core/navigation.js: navigation action strategylib/plugin/eventRegister.js: router wrappinglib/components/VuePageStack.js: page stack behaviorlib/runtime/pageStackRenderer.js: Vue renderer adapter
Tests
tests/unit/*.test.js: plugin and navigation strategy behaviortests/integration/vue-page-stack.test.js: page stack behavior against mounted Vue components
Invariants
These assumptions are important when changing the implementation:
- Navigation state must remain scoped to the installed app instance.
- Cached vnode reuse is only valid when route keys match.
- Back-navigation cache miss must degrade to fresh render safely.
- The renderer adapter should remain the only place that touches Vue renderer internals.
- Tests should cover both normal flows and degraded edge cases.
- Suspense and transition-wrapped route nodes should still compare and restore against the actual cached child vnode.
Maintenance Notes
When modifying stack behavior, run:
pnpm run test:run
pnpm run test:coverage
pnpm run lint
pnpm run build
When behavior looks wrong only after dependency upgrades, inspect these files first:
lib/runtime/pageStackRenderer.jslib/components/VuePageStack.jslib/core/navigation.js