Skip to content

Preact Router

The @nano_kit/preact-router package provides Preact integration for @nano_kit/router. It allows you to use the router’s powerful features like code splitting, Dependency Injection, and state management directly within your Preact application.

Install the package using your favorite package manager:

pnpm add @nano_kit/store @nano_kit/router @nano_kit/preact @nano_kit/preact-router

Basically, @nano_kit/preact-router re-exports everything from @nano_kit/router, so you can use all base router functions. However, it enhances some of them and provides new utilities specifically for Preact.

A typical setup looks like this:

import { render } from 'preact'
import { browserNavigation, router, layout, page, loadable, usePageSignal } from '@nano_kit/preact-router'
import { MainLayout } from './MainLayout'
/* Define routes config */
const routes = {
home: '/',
user: '/users/:id'
} as const
/* Define loader fallback */
const Loader = () => <div>Loading...</div>
/* Create navigation */
const [$location, navigation] = browserNavigation(routes)
/* Create page signal */
const $page = router($location, [
layout(MainLayout, [
page('home', loadable(() => import('./pages/Home'), Loader)),
page('user', loadable(() => import('./pages/User'), Loader))
])
])
function App() {
const Page = usePageSignal($page)
return Page ? <Page /> : null
}
/* Render App */
render(<App />, document.getElementById('root')!)

If you want to use DI-based setup, pass the router tokens through InjectionContextProvider:

import { render } from 'preact'
import { provide } from '@nano_kit/store'
import { InjectionContextProvider } from '@nano_kit/preact'
import { App, browserNavigation, router, Location$, Navigation$, Page$, Pages$ } from '@nano_kit/preact-router'
import { pages } from './pages'
import { routes } from './routes'
/* Define global routes types for DI */
declare module '@nano_kit/router' {
interface AppContext {
routes: typeof routes
}
}
/* Create navigation and page accessor */
const [$location, navigation] = browserNavigation(routes)
const $page = router($location, pages)
/* Render App with DI */
render((
<InjectionContextProvider
context={[
provide(Location$, $location),
provide(Navigation$, navigation),
provide(Page$, $page) // required for App component
]}
>
<App />
</InjectionContextProvider>
), document.getElementById('root')!)

The router function in this package is an enhanced version of the core router. It supports Preact components as views and correctly handles nested layouts using the Outlet component.

import { router, page } from '@nano_kit/preact-router'
const $page = router($location, [
page('home', HomePage),
page('user', UserPage)
])

These hooks return the currently matched page component.

Without DI, use usePageSignal with the $page accessor returned by router():

import { usePageSignal } from '@nano_kit/preact-router'
function App() {
const Page = usePageSignal($page)
return Page ? <Page /> : null
}

With DI, use usePage and let it read Page$ from the current injection context:

import { usePage } from '@nano_kit/preact-router'
function App() {
const Page = usePage()
return Page ? <Page /> : null
}

App is a ready-made Preact component that reads Page$ from the current injection context and renders the matched page.

import { provide } from '@nano_kit/store'
import { router, page, Page$, App } from '@nano_kit/preact-router'
import { InjectionContextProvider } from '@nano_kit/preact'
const $page = router($location, [
page('home', HomePage),
page('user', UserPage)
])
<InjectionContextProvider
context={[
provide(Page$, $page)
]}
>
<App />
</InjectionContextProvider>

Used within Layout components to define where the nested child route should be rendered.

import { Outlet } from '@nano_kit/preact-router'
export function MainLayout() {
return (
<div className='layout'>
<Sidebar />
<main>
{/* Nested page will be rendered here */}
<Outlet />
</main>
</div>
)
}

These hooks intercept clicks on native <a> elements and route them through the router navigation layer. Use them when you want to use the router without a custom link component.

Without DI, pass the navigation instance explicitly:

import { useNavigationListenLinks, usePageSignal } from '@nano_kit/preact-router'
function App() {
const Page = usePageSignal($page)
useNavigationListenLinks(navigation)
return Page ? <Page /> : null
}

With DI, use useListenLinks and let it read Navigation$ from the current injection context:

import { useListenLinks } from '@nano_kit/preact-router'
function App() {
const Page = usePage()
useListenLinks()
return Page ? <Page /> : null
}

linkComponent creates a type-safe Link component bound to a specific navigation instance and paths object. Link is the DI-based variant that reads router dependencies from the current injection context.

import { linkComponent, buildPaths, preloadable } from '@nano_kit/preact-router'
/* Create Link component */
const Link = linkComponent(
navigation,
buildPaths(routes),
/* Optional: enable preloading on interaction */
[preloadable(pages)]
)
/* Usage */
<Link to='user' params={{ id: '123' }} preload>
View User
</Link>

For DI usage, use the built-in Link component inside InjectionContextProvider:

import { Link } from '@nano_kit/preact-router'
<Link to='user' params={{ id: '123' }}>
View User
</Link>

The DI variant uses the provided Navigation$ and Paths$ tokens under the hood.

These link extensions enable page preloading on hover and focus.

Without DI, use preloadable when creating your custom Link component. It accepts either the pages array or a function returning it:

import { linkComponent, buildPaths, preloadable } from '@nano_kit/preact-router'
import { pages } from './pages'
const Link = linkComponent(
navigation,
buildPaths(routes),
[
/* true = default value of Link's preload prop */
preloadable(() => pages, true)
]
)

With DI, use useLinkComponentPreload to enable the same behavior for the built-in Link component:

import { useLinkComponentPreload } from '@nano_kit/preact-router'
function App() {
const Page = usePage()
/* true = default value of built-in Link's preload prop */
useLinkComponentPreload(true)
return Page ? <Page /> : null
}

These link extensions enable automatic aria-current handling for active links.

Without DI, use ariaCurrent when creating your custom Link component:

The optional second argument is a predicate that receives the link URL and current location and decides whether the link should be treated as current. By default, it checks url.pathname === location.pathname.

import { ariaCurrent, linkComponent, buildPaths } from '@nano_kit/preact-router'
const Link = linkComponent(
navigation,
buildPaths(routes),
[ariaCurrent($location)]
)

With DI, use useLinkComponentAriaCurrent to enable the same behavior for the built-in Link component:

It accepts the same optional predicate, but reads the current location from DI instead of taking $location explicitly.

import { useLinkComponentAriaCurrent, usePage } from '@nano_kit/preact-router'
function App() {
const Page = usePage()
useLinkComponentAriaCurrent()
return Page ? <Page /> : null
}

These hooks synchronize the current page’s Head$ descriptors.

Without DI, use usePageSyncHead with an explicit $page accessor:

import { usePageSyncHead } from '@nano_kit/preact-router'
function App() {
const Page = usePageSignal($page)
usePageSyncHead($page)
return Page ? <Page /> : null
}

With DI, use useSyncHead and let it read Page$ from the current injection context:

import { useSyncHead } from '@nano_kit/preact-router'
function App() {
const Page = usePage()
useSyncHead()
return Page ? <Page /> : null
}

useLocation subscribes to the current route location from the injection context.

import { useLocation } from '@nano_kit/preact-router'
function RouteInfo() {
const location = useLocation()
return <span>{location.pathname}</span>
}

useNavigation returns the navigation API from the injection context.

import { useNavigation } from '@nano_kit/preact-router'
function BackButton() {
const navigation = useNavigation()
return <button onClick={() => navigation.back()}>Back</button>
}

usePaths returns the typed path builders derived from your route definitions.

import { usePaths } from '@nano_kit/preact-router'
function UserLink({ id }: { id: string }) {
const paths = usePaths()
return <a href={paths.user({ id })}>View user</a>
}

useCanGoBack subscribes to whether back navigation is currently possible.

import { useCanGoBack, useNavigation } from '@nano_kit/preact-router'
function BackButton() {
const navigation = useNavigation()
const canGoBack = useCanGoBack()
return (
<button disabled={!canGoBack} onClick={() => navigation.back()}>
Back
</button>
)
}