Skip to content

Latest commit

 

History

History
169 lines (129 loc) · 4.36 KB

routing.md

File metadata and controls

169 lines (129 loc) · 4.36 KB

Core: Routing

The Askr router uses one route model across SPA, SSR, and SSG:

  • createRouteRegistry() captures route definitions
  • group() defines inherited layout and access behavior
  • page() defines a renderable route shell with nested child routes
  • index() defines the default child route inside a page host
  • route() defines pages and returns a typed RouteRef
  • fallback() defines the nearest pathful miss route

Register routes

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 with Outlet().
  • Child route() calls inside page() 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>
  );
}

Read the route

Inside a component, call currentRoute():

import { currentRoute } from '@askrjs/askr/router';

function PostPage() {
  const snap = currentRoute();
  return <article>{snap.params.slug}</article>;
}

Route options

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],
});

Group inheritance

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);
});

Path syntax

  • Static: /settings
  • Param: /posts/{slug}
  • Wildcard: /files/*
  • Catch-all: /*

Always use {name} for params.

Navigation

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>;