Advanced Patterns
Route guards
A guard runs before a route resolves. Return true to
allow it, or a redirect() to send the visitor elsewhere. Because the
redirect target is checked against your route map, a mistyped path fails at
compile time rather than shipping a dead link.
import { createRouter, redirect } from 'routekit';
const router = createRouter({ routes: { '/dashboard': { // A guard runs before the route resolves. Return a redirect to // bounce unauthenticated users — fully type-checked against your // route map, so a typo'd path is a compile error, not a 404. guard: ({ session }) => session ? true : redirect('/login'), component: () => import('./Dashboard'), }, },});Parallel data loaders
Pair a lazy component with a loader and routekit fires both
at once — the network request for data overlaps the network request for code,
so a route that needs both is no slower than a route that needs either.
'/users/:id': { // Loaders fire in parallel with the lazy component import, so data // and code arrive together. The resolved value is typed and passed // to the component as `data`. loader: ({ params }) => fetchUser(params.id), component: () => import('./User'),}Type-safe params
The path pattern is the type. routekit reads :id out of the
string literal and requires it at every call site — no generated types, no
codegen step, no drift between your routes and your navigation calls.
// routekit infers params from the path pattern at the type level.router.navigate('/users/:id', { id: '42' }); // okrouter.navigate('/users/:id', {}); // TS error: missing 'id'router.navigate('/uesrs/:id', { id: '42' }); // TS error: no such routeNested layouts
Compose layouts by nesting routers. A parent router owns the chrome (sidebar, header); each child router swaps only its own outlet. Because routers are plain objects, you can split them across files and lazy-load an entire section's router the first time a visitor enters it.
- Define a child router for the section and export it.
- Mount it at the parent route's
componentas a lazy import. - The child renders into the parent's outlet; URLs compose automatically.
Testing routes
routekit/testing ships a memory router — no DOM, no jsdom history
shims. Drive navigation directly and assert on router.current.
Guards, loaders, and redirects all run exactly as they do in the browser.
import { createMemoryRouter } from 'routekit/testing';
test('redirects guests away from /dashboard', async () => { const router = createMemoryRouter(routes, { session: null }); await router.navigate('/dashboard'); expect(router.current.path).toBe('/login');});