Skip to content

Advanced

Router provides built-in support for code splitting and lazy loading of pages using the loadable function.

Defines a lazy-loaded component or page that is fetched only when needed. It accepts a module loader function (returning a promise) and an optional fallback view to display while loading.

The loaded module must export a default component/view and optionally a Stores$ function for data prefetching and dehydration in SSR.

import { router, loadable, page } from '@nano_kit/router'
/* Fallback component */
const Loader = () => 'Loading...'
export const $page = router($location, [
page('home', loadable(() => import('./pages/Home.js'), Loader)),
page('user', loadable(() => import('./pages/User.js'), Loader))
])

Forces the loading of a specific page’s code by its route name. This is useful for preloading the next likely page (e.g., on link hover).

import { loadPage } from '@nano_kit/router'
import { routes } from './routes.js'
/* Preload user page code */
await loadPage(routes, 'user')

Loads the code for all defined pages in the route tree. This is commonly used in server-side rendering (SSR) to ensure all async components are resolved before rendering the application.

import { loadPages } from '@nano_kit/router'
import { routes } from './routes.js'
/* Preload all pages */
await loadPages(routes)

The router ships with a reactive head management API for controlling document metadata (title, meta tags, link tags, scripts) on a per-page basis.

Add a Head$ factory to a page module to declare which head descriptors it provides. The router exposes it on the current $page() ref, where syncHead can read and apply it.

import { Location$, title, meta } from '@nano_kit/router'
import { inject } from '@nano_kit/store'
export function Head$() {
const $location = inject(Location$)
return [
title(() => `User ${$location().params.id}`),
meta({ name: 'description', content: 'User profile page' })
]
}
export default function UserPage() { /* ... */ }

syncHead subscribes to the current page accessor and applies all head descriptors reactively. Call it once during app initialization.

import { syncHead } from '@nano_kit/router'
const $page = router($location, pages)
syncHead($page)

You can pass an optional InjectionContext as the second argument to resolve Head$ factories with DI:

syncHead($page, context)
FunctionUpdates
title($value)<title> in <head>
lang($value)<html lang>
dir($value)<html dir>
link(props)<link> in <head>
meta(props)<meta> in <head>
script(props)<script> in <head>

title, lang, and dir accept a plain value or a reactive accessor. For link and meta, only certain props support reactive accessors — the ones that are likely to change at runtime:

  • linkhref, media, disabled, title
  • metacontent, media
import { title, lang, link, meta } from '@nano_kit/router'
export function Head$() {
const $user = inject(User$)
return [
title(() => `${$user().name} — My App`),
lang('en'),
link({ rel: 'canonical', href: () => $user().profileUrl }),
meta({ property: 'og:title', content: () => $user().name })
]
}

Since scroll restoration and behavior can vary significantly between applications, the library provides a set of “Do It Yourself” (DIY) utilities instead of a one-size-fits-all solution. You can combine these tools to implement the exact scrolling behavior your app needs.

The router does not reset scroll automatically. Use resetScroll to move the window scroll position to the top (0, 0), for example when navigating to a new page.

import { onMountEffect } from '@nano_kit/store'
import { resetScroll } from '@nano_kit/router'
const { $route } = $location
/* Reset scroll whenever the route changes */
onMountEffect($route, () => {
$route()
resetScroll()
})

To skip scroll resets for specific navigations, you can use the navigation action as part of your own scroll policy. For example, this pattern resets scroll for push navigations but keeps the current scroll position for replace navigations:

import { onMountEffect } from '@nano_kit/store'
import { ReplaceHistoryAction, resetScroll } from '@nano_kit/router'
const { $route, $action } = $location
/* Reset scroll on route changes, except replace navigations */
onMountEffect($route, () => {
$route()
if ($action() !== ReplaceHistoryAction) {
resetScroll()
}
})
/* Keeps the current scroll position with the effect above */
navigation.replace('/characters?page=2')

Scrolls the window to a specific element identified by a URL hash (e.g., #section). It handles looking up the element by ID or name and scrolling it into view.

import { onMountEffect } from '@nano_kit/store'
import { scrollToAnchor } from '@nano_kit/router'
/* Scroll to element with id="features" smoothly */
scrollToAnchor('#features', { behavior: 'smooth' })
const { $hash } = $location
/* Or automatically handle hash from location */
onMountEffect($hash, () => {
const hash = $hash()
if (hash) {
scrollToAnchor(hash)
}
})

A utility class that saves and restores scroll positions using sessionStorage. It helps maintain the user’s scroll position when navigating back and forth within the history.

import { ScrollRestorator } from '@nano_kit/router'
/* 1. Create instance (optional prefix) */
const scrollRestorator = new ScrollRestorator('my-app-scroll-')
/* 2. Save scroll position before leaving the current view */
/* (e.g., in a cleanup function or before navigation) */
scrollRestorator.save($location())
/* 3. Restore scroll position when returning to a view */
const restored = scrollRestorator.restore($location())
if (!restored) {
/* If no position was saved, default to top */
resetScroll()
}

The transition method on the navigation object allows you to intercept and control the navigation process. This is powerful for implementing global behaviors like scroll restoration, page transitions, or navigation guards (confirmation dialogs).

By default, it simply executes the transition. You can override it to add custom logic.

import { browserNavigation } from '@nano_kit/router'
const [$location, navigation] = browserNavigation(routes)
/* Example 1: Scroll Restoration */
navigation.transition = (proceed, nextLocation, prevLocation) => {
/* Save scroll position for the page we are leaving */
scrollRestorator.save(prevLocation)
/* Proceed with the navigation */
proceed(nextLocation)
/* Restore scroll position for the new page */
scrollRestorator.restore(nextLocation)
}
/* Example 2: Navigation Guard / Confirmation */
navigation.transition = (proceed, nextLocation, prevLocation) => {
const isDirty = formIsDirty()
if (!isDirty || confirm('Are you sure you want to leave? Unsaved changes will be lost.')) {
proceed(nextLocation)
}
}

The arguments are:

  1. proceed: A function that must be called to complete the navigation. Pass nextLocation to it.
  2. nextLocation: The target location object (or null if unknown).
  3. prevLocation: The current location object before navigation.

The basePath helper allows you to prefix all routes with a common base path. This is useful when your application is hosted in a subdirectory (e.g., GitHub Pages) or behind a specific path.

import { basePath } from '@nano_kit/router'
/* All routes will be prefixed with /admin */
const routes = basePath('/admin', {
dashboard: '/',
users: '/users'
})
/* Resulting patterns: */
/* dashboard -> /admin/ */
/* users -> /admin/users */

A utility to update parts of a URL string (pathname, search, or hash) while preserving the rest. It accepts a current href and an update object or string.

import { updateHref } from '@nano_kit/router'
const current = '/users?sort=name#top'
/* Update query params */
const next = updateHref(current, { search: '?sort=date' })
// /users?sort=date#top
/* Update path */
const moved = updateHref(current, { pathname: '/admins' })
// /admins?sort=name#top

Removes the trailing slash from a path string, ensuring consistent path handling.

import { removeTrailingSlash } from '@nano_kit/router'
removeTrailingSlash('/path/') // '/path'
removeTrailingSlash('/path') // '/path'

A helper function to handle click events on anchor tags (<a>) in a Single Page Application (SPA) way. It intercepts the click, prevents the default browser navigation, and instead calls navigation.push() or navigation.replace().

Use this if you need to build a custom Link component for your framework.

import { onLinkClick } from '@nano_kit/router'
import { navigation } from './router.js'
const handleClick = onLinkClick.bind(navigation)
function CustomLink({ href, children }) {
return <a href={href} onClick={handleClick}>{children}</a>
}

canGoBack returns an Accessor<boolean> that is true when back navigation is possible (i.e. the history stack has more than one entry).

import { inject } from '@nano_kit/store'
import { canGoBack } from '@nano_kit/router'
const [$location, navigation] = browserNavigation(routes)
const $canGoBack = canGoBack($location, navigation)
effect(() => {
console.log('Can go back:', $canGoBack())
})
/* Output: Can go back: false (initially) */
navigation.push('/users')
/* Output: Can go back: true */

The router provides global injection tokens for navigation state. Use them in stores or components to access routing data without passing signals as props.

  • Location$ - provides the current location signal. Token itself doesn’t have a default value — it should be provided to the context by the app.
  • Navigation$ - provides the navigation API. Like Location$, it should be provided by the app.
  • Page$ - provides the current page reference from the router. Should be provided by the app.
  • Pages$ - provides the array of page definitions passed to the router. Should be provided by the app.
  • Paths$ - provides typed path builder functions derived from the navigation. Depends on Navigation$.
  • CanGoBack$ - provides a boolean signal indicating if back navigation is possible. Depends on Location$ and Navigation$.

Not every token needs to be provided — it depends on your app’s needs. For example, if you don’t use path builders, you can skip providing Paths$.

The router uses TypeScript declaration merging to infer app-specific types across all tokens. Extend the AppContext interface in your project to enable typed params, paths, and components:

import type { routes } from './routes.js'
import type { MyComponent } from './types.js'
declare module '@nano_kit/router' {
interface AppContext {
routes: typeof routes // types Location$, Navigation$, Paths$
component: MyComponent // types Page$, Pages$
}
}
  • routes — must be typeof yourRoutesObject. Enables types for Location$, Navigation$, and Paths$ tokens based on your route definitions.
  • component — your framework’s component type (e.g. () => ReactNode). Enables typed $page().default in Page$.

If AppContext is not extended, all tokens fall back to generic types.