Routing
Define typed routes, layouts, navigation, loaders, policies, and metadata.
Example
Register a route once and let the same registry drive browser navigation, SSR, and static generation.
import { createRouteRegistry, group, route } from '@askrjs/askr/router';
export const registry = createRouteRegistry(() => {
group({ layout: AppLayout }, () => {
route('/', HomePage);
route('/projects/{projectId}', ProjectPage);
});
});Published props
Generated from the TypeScript declarations shipped by the installed package. Named types in the Type column define the accepted values.
LinkProps
Import from @askrjs/askr/router.
| Prop | Type | Default | Description |
|---|---|---|---|
aria-current | aria-current?: "time" | "page" | "step" | "location" | "date" | "true" | "false" | undefined; | — | Optional aria-current attribute for indicating current page/location. Use "page" for the current page in navigation. |
aria-label | aria-label?: string | undefined; | — | Optional aria-label for accessibility when link text isn't descriptive enough. |
children | children?: RenderableChild; | — | — |
class | class?: string | undefined; | — | — |
href | href?: string | undefined; | — | — |
onClick | onClick?: ((event: MouseEvent) => void) | undefined; | — | — |
onPress | onPress?: ((event: Event) => void) | undefined; | — | — |
rel | rel?: string | undefined; | — | Optional rel attribute for link relationships. Common values: "noopener", "noreferrer", "nofollow" |
target | target?: string | undefined; | — | Optional target attribute. Use "_blank" for new tab/window. |
to | to?: RouteDestination | undefined; | — | — |
ResolveProps
Import from @askrjs/askr/router.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children: (value: T) => RenderableChild; | — | — |
pending | pending?: RenderableChild; | — | — |
rejected | rejected?: RenderableChild | ((error: unknown) => RenderableChild); | — | — |
value | value: Deferred<T>; | — | — |
Route registry
Every route in an Askr app is declared through createRouteRegistry(), which takes a definition function and returns a RouteRegistry built by calling route(), page(), group(), index(), and fallback() inside that function. The registry is an ordinary value: export it, then hand it to createSPA, hydrateSPA, createStaticGen, or renderToString, so client rendering, hydration, SSG, and SSR all read the same declaration. Because nothing is stored globally, two registries can coexist — which is what lets a static build settle its own route tree without disturbing the one the browser boots from, and what keeps a server rendering concurrent requests off shared mutable state.
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 app's explicit RouteRegistry 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.