Skip to content
nav-router
v0.1.0

Your browser’s native router made powerful

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
acme.app/team/acme
navigate → /team/acme slowed to 0.4× speed for demo
Pick a tab to watch one navigation run, from the navigate event to the rendered page.

Your whole app, declared as one tree

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.

  • Typed params and query. Defaults and allowed values, coerced before your code sees them.
  • Named routes. Link with href('team', { org }), never by pasting strings together.
  • Lazy views. lazy(() => import('./page.js')) splits a route out of the main bundle.
Read the routes guide
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,
        },
      },
    },
  },
});

Built on what the platform already does

No history shim, no click interception, no <Link> component. Plain anchors and forms are navigations, because the browser says so.

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 })

Back and forward keep their data

Results ride on the history entry. Going back shows what was there, without fetching it again.

navigation.back()
// restored from the entry, no refetch

Guards run before the URL moves

A redirect goes straight to its target. A page the user can't open never enters history.

permission: ({ user }) => !!user,
signInRoute: 'signIn',

Small core, opt-in plugins

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);
Browse all plugins

Start with one route

npm i nav-router
Get started