Vue 3 Integration
Problem
Vue's reactivity system needs both an initial value and change notifications from the router. ref() on a deeply nested immutable object adds unnecessary tracking overhead.
Solution
Use shallowRef(router.getSnapshot()) initialized at module scope and update via router.subscribe(). Expose a single useRouter() composable.
ts
// router.ts
import { createRouter } from '@vielzeug/wayfinder';
import { readonly, shallowRef } from 'vue';
export const router = createRouter({
routes: {
home: { path: '/' },
settings: { path: '/settings' },
},
notFound: { data: () => ({ message: 'Not found' }) },
});
const state = shallowRef(router.getSnapshot());
router.subscribe((next) => {
state.value = next;
});
// Router actions are stable own properties and safe to return from the composable.
const { isActive, navigate, url } = router;
export function useRouter() {
return { isActive, navigate, state: readonly(state), url };
}vue
<!-- RouterView.vue — map route names to components in your adapter layer -->
<script setup lang="ts">
import { computed } from 'vue';
import { router, useRouter } from './router';
const views = router.createViewRegistry(
{ home: HomePage, settings: SettingsPage },
{ notFound: NotFoundPage },
);
const { state } = useRouter();
const component = computed(() => views.resolve(state.value));
</script>
<template>
<component :is="component" v-if="component" />
</template>vue
<!-- RouterLink.vue -->
<script setup lang="ts">
import { computed } from 'vue';
import { useRouter } from './router';
const props = defineProps<{
name: 'home' | 'settings';
}>();
const { isActive, navigate, state, url } = useRouter();
const href = computed(() => url(props.name));
const active = computed(() => {
void state.value;
return isActive(props.name);
});
</script>
<template>
<a :aria-current="active ? 'page' : undefined" :href="href" @click.prevent="() => navigate({ name: props.name })">
<slot />
</a>
</template>This gives a Vue-Router-like DX (useRouter() composable, link + view) without an adapter package.
Pitfalls
- Use
shallowRefnotref.RouteStateis a deep immutable object; deep tracking wastes memory and triggers false positives on object identity comparisons. isActive()reads the router snapshot. Addvoid state.valuein theactivecomputed (shown above) so Vue tracks every navigation.