Skip to content

Vue 3 Integration

Export a single useRouter() composable from router.ts. It returns reactive route state and bound router actions, so components import one symbol and call one composable.

ts
// router.ts
import { createRouter } from '@vielzeug/routeit';
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.state);
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.