Every navigation can be cancelled
Each route's data gets the navigation's AbortSignal. Click away mid-fetch and the request stops.
data: ({ params, signal }) =>
api.team(params.org, { signal })
A fully featured router with view thunks, typed arguments, async loading and everything you need to ship your next big app in only 11 kB br.
npm i nav-router
navigate event to the rendered page.
Paths, params, guards, data and views sit together on the route they belong to. Nested routes inherit what's above them, so a layout and its data load once while the pages under it change.
href('team', { org }), never by pasting strings together.lazy(() => import('./page.js')) splits a route out of the main bundle.
import { router, lazy } from 'nav-router';
export const app = router({
title: 'Acme',
layout: RootLayout,
index: { path: '/', view: HomePage },
routes: {
team: {
path: '/team/:org',
layout: TeamLayout,
!! data: ({ params, signal }) => api.team(params.org, { signal }),
routes: {
members: {
path: '/team/:org/members',
view: lazy(() => import('./members.js')),
},
settings: {
path: '/team/:org/settings',
!! permission: ({ user }) => user?.role === 'admin',
view: SettingsPage,
},
},
},
},
});
import { lazy } from 'nav-router';
import { router } from '@nav-router/react';
export const app = router({
title: 'Acme',
layout: RootLayout,
index: { path: '/', view: HomePage },
routes: {
team: {
path: '/team/:org',
layout: TeamLayout,
!! data: ({ params, signal }) => api.team(params.org, { signal }),
routes: {
members: {
path: '/team/:org/members',
view: lazy(() => import('./Members.jsx')),
},
settings: {
path: '/team/:org/settings',
!! permission: ({ user }) => user?.role === 'admin',
view: SettingsPage,
},
},
},
},
});
import { lazy } from 'nav-router';
import { router } from '@nav-router/solid';
export const app = router({
title: 'Acme',
layout: RootLayout,
index: { path: '/', view: HomePage },
routes: {
team: {
path: '/team/:org',
layout: TeamLayout,
!! data: ({ params, signal }) => api.team(params.org, { signal }),
routes: {
members: {
path: '/team/:org/members',
view: lazy(() => import('./Members.tsx')),
},
settings: {
path: '/team/:org/settings',
!! permission: ({ user }) => user?.role === 'admin',
view: SettingsPage,
},
},
},
},
});
No history shim, no click interception, no <Link> component. Plain anchors and forms are navigations, because the browser says so.
Each route's data gets the navigation's AbortSignal. Click away mid-fetch and the request stops.
data: ({ params, signal }) =>
api.team(params.org, { signal })
Results ride on the history entry. Going back shows what was there, without fetching it again.
navigation.back() // restored from the entry, no refetch
A redirect goes straight to its target. A page the user can't open never enters history.
permission: ({ user }) => !!user,
signInRoute: 'signIn',
Like day.js, the core does one job and plugins add the rest. Each one wraps a single moment in the router and calls through, so you only ship what you install.
import { Router } from 'nav-router';
import head from 'nav-router/plugins/head';
import viewTransitions from 'nav-router/plugins/view-transitions';
Router.extend(head);
Router.extend(viewTransitions);
headTitle, meta and canonical tags per page1.1 kB
view-transitionsEach page change inside a view transition0.2 kB
announcerTells screen readers the page changed0.8 kB
confirm-leaveAsks before leaving unsaved work1.5 kB
preload-intentWarms a route’s code on hover or focus0.4 kB
chunk-reloadRecovers when a deploy removes old chunks0.5 kB
timelineNavigations in the devtools performance panel0.9 kB
sitemapEvery route, for a crawler or generator0.2 kB
domString and element views, no framework1.1 kB
The core renders nothing. Each integration fills three hooks, create, update and destroy, so your routes stay the same whichever you pick.
nav-router
Plain DOM through the dom plugin: strings, elements or web components.
@nav-router/react
Route views as components, with hooks for the page and its pending state.
Preact@nav-router/preact
The React adapter’s shape at Preact’s size.
Solid@nav-router/solid
Route data as fine-grained stores that update only what changed.
Vue@nav-router/vue
Route views as Vue components, route data as reactive state.
Svelte@nav-router/svelte
Route views as Svelte components, route data as reactive state.
Semantic UI@nav-router/semantic
Route views as Templates or web components, route data as signals.
Your frameworkintegration guide
Add your framework with just:
createView() {}
updateView() {}
destroyView() {}
npm i nav-router