Svelte Integration
Problem
Svelte's $store auto-subscription expects store.subscribe(run) to invoke run immediately. router.subscribe() never synchronously emits the current snapshot, so it does not satisfy the Svelte store contract directly.
Solution
Wrap the router in readable(router.getSnapshot(), set => router.subscribe(set)) from svelte/store. This injects the current state as the initial value while router updates drive subsequent changes.
ts
// router.ts
import { createRouter } from '@vielzeug/wayfinder';
import { readable } from 'svelte/store';
const router = createRouter({
routes: {
home: { component: HomePage, path: '/' },
settings: { component: SettingsPage, path: '/settings' },
notFound: { component: NotFoundPage, path: '*' },
},
});
// Wraps the router in a Svelte-compatible readable store.
// `readable` calls set(initialValue) immediately, then router.subscribe drives updates.
export const routerState = readable(router.getSnapshot(), (set) => router.subscribe(set));
// Expose actions directly; router methods are safe to destructure.
export const { isActive, navigate, url } = router;svelte
<!-- RouterView.svelte -->
<script lang="ts">
import { routerState } from './router';
$: component = $routerState.matches.at(-1)?.component;
</script>
{#if component}
<svelte:component this={component} />
{/if}svelte
<!-- RouterLink.svelte -->
<script lang="ts">
import { isActive, navigate, routerState, url } from './router';
export let name: 'home' | 'settings' | 'notFound';
$: void $routerState;
$: href = url(name);
$: active = isActive(name);
function go(event: MouseEvent) {
event.preventDefault();
void navigate({ name });
}
</script>
<a aria-current={active ? 'page' : undefined} href={href} on:click={go}>
<slot />
</a>Using readable from svelte/store ensures the store gets its initial value synchronously and updates reactively on every navigation.
Pitfalls
- Passing
routerdirectly as a Svelte store violates Svelte's synchronous initial-emission contract. Always use thereadablewrapper. - The
readableteardown is called when all subscribers unsubscribe. Do not callrouter.dispose()inside it unless you want to permanently shut down routing (e.g., end-to-end test cleanup).