Routing
Define typed routes, layouts, navigation, loaders, policies, and metadata.
@askrjs/askr/router0.0.59
How to use routing
Register a route once and let the same registry drive browser navigation, SSR, and static generation.
- Import the published @askrjs/askr/router entrypoint.
- Keep routing configuration next to the component, route, or server composition root that owns it.
- Handle pending, unavailable, cancellation, and failure states, then verify the production path.
import { createRouteRegistry, group, route } from '@askrjs/askr/router';
export const registry = createRouteRegistry(() => {
group({ layout: AppLayout }, () => {
route('/', HomePage);
route('/projects/:projectId', ProjectPage);
});
});Route registry
Every route in an Askr app is registered through `registerRoutes()`, which takes a definition function and builds a `RouteManifest` out of it by calling `route()`, `page()`, `group()`, `index()`, and `fallback()` inside that function. The manifest isn't a side effect you have to wire up yourself — call `getManifest()` afterward and hand it straight to `createSPA`, `hydrateSPA`, or `renderToString`, so the same registered routes drive client rendering, hydration, and SSR from one source. `createRouteRegistry()` gives you the lower-level `{ manifest, routes }` pair if you need to inspect route records directly, and `clearRoutes()` resets the registry, which matters mostly in tests.
Route groups
`group(options, fn)` nests a block of route declarations under a shared layout and shared access rules without repeating them on every child route. The `layout` you pass in `GroupHelperOptions` wraps whatever the group renders, and `auth`/`policies` on the group apply to every route declared inside its callback. Groups compose: nesting one group inside another builds up a `layoutChain` and a merged set of policies that Askr walks top-down when it resolves a request, so a route three groups deep still only needs to declare what's specific to it.
Navigation lifecycle
A navigation in Askr resolves through `resolveRouteRequest()`, which matches the target against the manifest and returns either a `RouteRenderResult`, an `AccessRedirectDecision`, an `AccessDenyDecision`, or `null` if nothing matches. That result already reflects any policy checks — redirects and denials are decided before a component ever renders, not caught afterward. Each `RouteContext` handed through the pipeline carries an `AbortSignal`, so loaders and preload functions tied to a route that gets superseded by a newer navigation can be cancelled instead of racing to finish and clobber the current view.
Delivery modes
`RouteMode` is one of `'spa'`, `'ssr'`, or `'ssg'`, and it's threaded through `RouteContext` so a loader or a policy can behave differently depending on how the current request is being served. The same route declarations work across all three modes — you don't write separate route trees for server rendering versus the client. SSG specifically leans on the `entries` option in `RouteOptions`, which returns the list of param combinations to pre-render as static pages at build time.