The Askr router uses one route model across SPA, SSR, and SSG:
createRouteRegistry()captures route definitionsgroup()defines inherited layout and access behaviorpage()defines a renderable route shell with nested child routesindex()defines the default child route inside a page hostroute()defines pages and returns a typedRouteReffallback()defines the nearest pathful miss route
import { requireAnonymous, requireRole, requireUser } from '@askrjs/auth';
import {
createRouteRegistry,
fallback,
group,
index,
Outlet,
page,
route,
} from '@askrjs/askr/router';
import AppLayout from './layouts/app-layout';
import AuthLayout from './layouts/auth-layout';
import Home from './routes/home';
import Dashboard from './routes/dashboard';
import Login from './routes/login';
import NotFound from './routes/not-found';
export const registry = createRouteRegistry(() => {
group({ layout: AppLayout }, () => {
route('/', Home);
page('/docs/components', DocsComponentsPage, () => {
index(DocsComponentsOverview);
route('tabs', DocsComponentsTabs);
route('pills', DocsComponentsPills);
});
group({ layout: AuthLayout, auth: requireAnonymous() }, () => {
route('/login', Login);
});
group({ auth: requireUser() }, () => {
route('/dashboard', Dashboard);
});
fallback(NotFound);
});
});Use group() and page() for different jobs:
group()is behavioral and pathless.page()is pathful and renderable.group({ layout })wraps matched routes from the outside.page()renders only when its pathname subtree is active and places child content withOutlet().- Child
route()calls insidepage()must be relative. index()creates the concrete default child route at the page pathname.
Fallback handling is pathful: fallback() registers either the root miss route
or a page-local miss route. group() can wrap fallback rendering, but it does
not define fallback scope because it is pathless.
Inside a page host, render the active child with Outlet():
import { Outlet } from '@askrjs/askr/router';
function DocsComponentsPage() {
return (
<section>
<h1>Components</h1>
<Outlet />
</section>
);
}Inside a component, call currentRoute():
import { currentRoute } from '@askrjs/askr/router';
function PostPage() {
const snap = currentRoute();
return <article>{snap.params.slug}</article>;
}route('/posts/{slug}', PostPage, {
auth: requireUser(),
loader: ({ params }) => fetchPost(params.slug),
entries: async () => getPosts().map((post) => ({ slug: post.slug })),
policies: [requireVerifiedEmail()],
search: PostSearch,
meta: { title: 'Post', description: 'A published post.' },
actions: [updatePostAction],
});Groups are pathless scopes. Child routes keep absolute paths.
group({ layout: RootLayout }, () => {
route('/', HomePage);
group({ auth: requireUser() }, () => {
route('/dashboard', DashboardPage);
group({ layout: AdminPanel, auth: requireRole('admin') }, () => {
route('/admin/users', AdminUsersPage);
route('/admin/settings', AdminSettingsPage);
});
});
fallback(NotFoundPage);
});- Static:
/settings - Param:
/posts/{slug} - Wildcard:
/files/* - Catch-all:
/*
Always use {name} for params.
Construct application-owned links from route references. Required params and schema-backed search values are validated before a destination is returned.
import { Link, navigate, to } from '@askrjs/askr/router';
import { postRoute } from './routes';
navigate('/dashboard');
navigate('/users/42', { replace: true });
navigate('/upload/confirm', { state: { uploadId: 'pending-42' } });
const destination = to(postRoute, { slug: 'release' }, { view: 'summary' });
<Link to={destination}>Release post</Link>;Raw href remains available for intentionally untyped or external links.
Link is the headless routing primitive. Applications using the optional
@askrjs/themes package can import NavLink from
@askrjs/themes/components when they want a styled navigation link that
tracks the active route automatically:
import { NavLink } from '@askrjs/themes/components';
<NavLink href="/settings">Settings</NavLink>;