Askr
Routing & Data

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.

  1. Import the published @askrjs/askr/router entrypoint.
  2. Keep routing configuration next to the component, route, or server composition root that owns it.
  3. 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.

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.