A router for apps that got serious.

Every app has routes.
Few apps really know them.

Cookbook Router turns routes into knowledge for links, navigation, params, search, middleware, layouts, validation, testing, and SSR.

Harpy Docs mascotHarpy Docs mascot

One source

One route. One truth.

Matching, links, URL state, middleware, rendering, tests, and SSR all answer to the same route contract.

End-to-end typing

{id:int} means number. Everywhere.

Params, search, and hash are inferred from the route contract. Wrong values fail in links and navigation before the app runs.

Build integration

Fits your build.

Generate and validate route artifacts during development and production builds.

Vite · Webpack · Rspack · Rollup · esbuild · Bun

The actual problem

Routing already shapes the app.
Your router should know it.

A production route is a path, a parser, an access rule, a navigation target, a layout decision, a test fixture, and an SSR concern. Spread that knowledge across the app and flexibility becomes maintenance. Five places to update. Six ways to be wrong.

Understand the route model

Refactor without archaeology

A route ID that survives when a pathname changes.

Stop copying paths into links, redirects, middleware, tests, and components. Navigate through a contract instead of hunting strings across the repository.

Explore typed contracts

Validate before rendering

Pages receive verified URL state.

Params, search values, and hash state are parsed against the route definition before application code depends on them.

Explore URL state

Put policy where it belongs

Authorization runs before the page.

Middleware can redirect, rewrite, cancel, or return a response before navigation commits. No access-control effects pretending to be architecture.

Explore middleware

Model the real interface

Layouts, slots, and modals stay routable.

Represent shells, sidebars, modal routes, previews, and split views in the route tree instead of rebuilding routing behavior around it.

Explore routed layouts

One declaration. Several guarantees.

Define what the route means.
Make the whole app answer to it.

The route tree is not a list of screens. It is the contract shared by runtime navigation, React rendering, generated types, tests, builds, and the server.

routes.ts

import { defineRoutes, lazyRouteView } from '@cookbook/router';const UserPage = lazyRouteView(()=> import("./pages/user-page.tsx"));export const routes = defineRoutes([  {    id: 'root',    path: '/',    layout: {      view: AppShell,    },    children: [      {        id: 'users.show',        path: 'users/{id:int}',        search: {          tab: {            type: 'enum',            values: ['profile', 'settings'],            default: 'profile',          },        },        middleware: [requireAuth],        view: UserPage,        meta: {          title: 'User',          requiresAuth: true,        },      },    ],  },] as const);

user-page.tsx

import { Link, useParams, useSearchParams } from '@cookbook/router-react';export function UserLink() {  return (    <Link      to="users.show"      params={{ id: 42 }}      search={{ tab: 'settings' }}      prefetch="mount"    >      Open settings    </Link>  );}export function UserPage() {  const params = useParams('users.show');  const search = useSearchParams('users.show');  // params.id: number  // search.tab: 'profile' | 'settings'}

Route ID

users.show

Path param

id: number

Search state

profile | settings

Policy

requireAuth

Live demo

Routing under pressure. Try the demo.

Open the demo and move through the parts routers usually flatten: shell layouts, modal routes, search params, auth redirects, lazy loading, error fallbacks, prefetching, and dirty-form blockers. The route contract is not sitting in a README. It is driving the app.

Try the demo

Built for application routing

More than matching a pathname to a component.

The happy path is easy in every router. Cookbook Router focuses on what happens after the application grows teeth.

Typed params, search, and hash

Declare URL state beside the route and consume parsed values instead of raw strings.

Middleware and lifecycle

Handle authorization, analytics, redirects, rewrites, cancellation, audit trails, and navigation policy.

Layouts, outlets, and slots

Build nested application shells and named routed surfaces without turning layout structure into hidden component state.

Route intercepts

Open route-driven modals, previews, and split views while preserving a navigable destination.

Preloading and lazy views

Preload route data and modules intentionally, then integrate lazy route views with React.

SSR and hydration

Use static routing, serialization, hydration checks, and the same route contracts on the server and client.

Validation and diagnostics

Reject duplicate IDs, invalid route structures, malformed redirects, missing params, and unsafe generated output.

Generated contracts

Generate TypeScript contracts, declarations, manifests, and composable route modules from static route definitions.

Generation where builds happen

Make the route contract part of the build.

Plugins that runs the same generation and validation pipeline during development and production builds, so invalid routes fail early and generated artifacts stay in sync.

Choose a bundler plugin

Pick your entry point

Read less. Find the page that moves the work forward.

Stop routing by guesswork

Give the application one route contract and make every layer answer to it.

Start with the core. Add React when you render with React. Add generation when you want typed route IDs, params, search, hash, autocomplete, and build-time validation.