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';
const router = createRouter({
routes: {
home: { component: HomePage, path: '/' },
settings: { component: SettingsPage, path: '/settings' },
notFound: { component: NotFoundPage, path: '*' },
},
});
const state = shallowRef(router.getSnapshot());
router.subscribe((next) => {
state.value = next;
});
// Stable bound references, safe to return from the composable.
const isActive = router.isActive.bind(router);
const navigate = router.navigate.bind(router);
const url = router.url.bind(router);
export function useRouter() {
return { isActive, navigate, state: readonly(state), url };
}vue
<!-- RouterView.vue -->
<script setup lang="ts">
import { computed } from 'vue';
import { useRouter } from './router';
const { state } = useRouter();
const component = computed(() => state.value.matches.at(-1)?.component);
</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' | 'notFound';
}>();
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()readshistory.locationdirectly, not the reactive ref. Addvoid state.valuein theactivecomputed (shown above) to force recomputation on every navigation.