@isorouter/svelte
Svelte 5 bindings for
@isorouter/core —
a lightweight SPA router built on the browser
Navigation API.
<Router>, <Outlet>, <Link> and getRouter() wrap the core router's
immutable-snapshot external store with Svelte 5's createSubscriber: reading
router.current inside a $derived, $effect or the template subscribes to
commits, and the subscription is torn down automatically once nothing reads it
anymore — no manual $effect, no leaks.
Full documentation →
Live demo on StackBlitz →
Install
npm install @isorouter/svelte@isorouter/core is a regular dependency and is installed automatically.
Requirements
- Svelte ≥ 5.7.
- Everything in
@isorouter/core's Requirements — notably the Navigation API. Load a polyfill if you need to support older engines, before<Router>mounts (it callsrouter.start()for you).
Quick start
// router.ts
import { createRouter, lazy } from "@isorouter/svelte";
import AppLayout from "./AppLayout.svelte";
import Home from "./Home.svelte";
import About from "./About.svelte";
import DashboardLayout from "./DashboardLayout.svelte";
import Overview from "./Overview.svelte";
import Settings from "./Settings.svelte";
export const router = createRouter([
{
path: "/",
component: AppLayout,
children: [
{ index: true, title: "Home", component: Home },
{ path: "about", title: "About", component: About },
{
path: "dashboard",
component: DashboardLayout,
children: [
{ index: true, component: Overview },
{ path: "settings", component: Settings },
],
},
],
},
] as const); // `as const` is required for type-safe navigation
declare module "@isorouter/svelte" {
interface Register {
router: typeof router;
}
}// main.ts
import { mount } from "svelte";
import App from "./App.svelte";
mount(App, { target: document.getElementById("app")! });<!-- App.svelte -->
<script lang="ts">
import { Router } from "@isorouter/svelte";
import { router } from "./router";
</script>
<Router {router}>
{#snippet notFound()}
<p>Not found</p>
{/snippet}
</Router><!-- DashboardLayout.svelte -->
<script lang="ts">
import { Outlet } from "@isorouter/svelte";
</script>
<h1>Dashboard</h1>
<Outlet />createRouter returns a SvelteRouter wrapping the core router, with the
component type fixed to Svelte's Component. <Router> calls
router.start() on mount and router.stop() on unmount.
Components
<Router>
<Router {router}>
{#snippet loading()}<Spinner />{/snippet}
{#snippet notFound()}<NotFound />{/snippet}
{#snippet error(err)}<ErrorPage error={err} />{/snippet}
</Router>router— aSvelteRouterinstance fromcreateRouter.notFound— rendered whenrouter.current.status === "not-found".error— called withrouter.current.errorwhenrouter.current.status === "error".loading— rendered whenever there's no matched root component yet (e.g. before the first commit) and neithererrornornotFoundapplies.
Snippet priority: error > notFound > matched component > loading.
Otherwise renders the root matched component, router.current.components[0].
<Outlet>
Renders the next component in the matched chain at the current nesting depth. Used inside a layout component to render its matched child route; renders nothing when there is no matching child. The layout instance stays mounted across child navigations.
<Link>
<Link href="/dashboard" activeClass="active" exact>Dashboard</Link>A plain <a> — the Navigation API intercepts the click, so modifier-clicks,
target="_blank" and downloads behave natively. Any other attributes are
forwarded to the <a>.
href— the target path.class— merged withactiveClass.activeClass— appended toclasswhenrouter.isActive(href, { exact })(default"active").exact— when set, only an exact URL match is considered active (without it, a parent link stays active on all child routes).
When active, also sets aria-current="page". Must be used within <Router>
(or <Outlet>).
getRouter()
<script lang="ts">
import { getRouter } from "@isorouter/svelte";
const router = getRouter();
</script>
<p>{router.current.params.city}</p>Reads the SvelteRouter instance from context. Must be used within <Router>
(or <Outlet>). router.current is a getter — read it inside a $derived,
$effect or the template to subscribe to commits; the subscription is dropped
once nothing reads it. router.navigate, router.back, router.forward and
router.isActive are also available on the instance.
Route title
Routes accept an optional title — a string or a function:
{ path: "about", title: "About", component: About }
// Dynamic — receives GuardContext with params, url, signal
{ path: "users/:id", title: (ctx) => `User #${ctx.params.id}`, component: User }Type-safe navigation
Declare routes as const and augment Register in the same file — then
router.navigate only accepts known paths and getRouter() returns the
concrete type everywhere:
// router.ts
export const router = createRouter([...] as const);
declare module "@isorouter/svelte" {
interface Register { router: typeof router; }
}See @isorouter/core's Type-safe navigation.
License
MIT Mykhailo Pidkhvatylin