alepha@docs:~/docs/guides/frontend$
cat 2-routing.md
4 min read
Last commit:

#Routing

Alepha uses the $page primitive to define React routes. It is a superset of $route designed specifically for React pages with support for data loading, code splitting, SSR, SSG, nested routing, and type-safe parameters.

#Setup

typescript
1import { $page } from "alepha/react/router";

Routes are defined as class properties. The class is registered with the Alepha instance in your entry files.

#Defining Pages

A complete example from a real Alepha application:

typescript
 1import { t } from "alepha"; 2import { $page } from "alepha/react/router"; 3import { $client } from "alepha/server/links"; 4import type { CountApi } from "./CountApi.ts"; 5  6export class AppRouter { 7  countApi = $client<CountApi>(); 8  9  home = $page({10    head: { title: "Home" },11    schema: {12      query: t.object({13        name: t.text({ default: "Alepha" }),14      }),15    },16    loader: async ({ query }) => {17      return {18        greeting: `Hello, ${query.name} SSR!`,19        count: await this.countApi.inc().then((result) => result.count),20      };21    },22    lazy: () => import("./Home.tsx"),23  });24 25  about = $page({26    head: { title: "About" },27    path: "/about",28    lazy: () => import("./About.tsx"),29  });30}

#Page Options

#path

URL pattern with parameter support. If omitted, defaults to the root (/).

typescript
1path: "/users/:id"2path: "/blog/:slug"

#schema

Type-safe URL parameters and query strings using TypeBox schemas.

typescript
1schema: {2  params: t.object({ id: t.integer() }),3  query: t.object({ tab: t.optional(t.text()) }),4}

Parameters and query values are validated and typed in the loader and component props.

#loader

Server-side data fetching function. Receives typed params, query, and parent props. The returned data is passed to the component as props. In SSR, data is serialized on the server and hydrated on the client.

typescript
1loader: async ({ params, query }) => {2  const user = await this.userApi.getUser(params.id);3  return { user };4}

#component and lazy

Provide the React component to render. Use lazy for code splitting (recommended):

typescript
1// Code splitting (recommended)2lazy: () => import("./UserProfile.tsx")3 4// Direct component5component: ({ user }) => <div>{user.name}</div>

Lazy-loaded modules must use a default export.

#head

Set document head tags (title, meta, etc.). Can be static or dynamic:

typescript
1// Static2head: { title: "About Us" }3 4// Dynamic, based on loader data5head: (props) => ({6  title: props.user.name,7  description: `Profile of ${props.user.name}`,8})

#static

Pre-render the page at build time (SSG). On the server, acts as a cached page.

typescript
 1// Simple static page 2static: true 3  4// With predefined entries 5static: { 6  entries: [ 7    { params: { slug: "hello-world" } }, 8    { params: { slug: "getting-started" } }, 9  ],10}

#client

Force client-side only rendering (no SSR). Uses the <ClientOnly /> component internally.

typescript
1client: true

#cache

Server-side caching configuration. Automatically set when static: true.

typescript
1cache: {2  store: {3    provider: "memory",4    ttl: [1, "hour"],5  },6}

#can

Permission-based access control. Return false to block access (results in 403).

typescript
1can: () => userHasPermission("admin")

#Nested Routing

Define parent-child relationships between pages using the children or parent option. Parent pages render child content using the <NestedView /> component.

typescript
 1import { $page } from "alepha/react/router"; 2import { NestedView } from "alepha/react/router"; 3  4class AppRouter { 5  layout = $page({ 6    path: "/app", 7    component: () => ( 8      <div> 9        <nav>Sidebar</nav>10        <main>11          <NestedView />12        </main>13      </div>14    ),15    children: () => [this.dashboard, this.settings],16  });17 18  dashboard = $page({19    path: "/dashboard",20    parent: this.layout,21    lazy: () => import("./Dashboard.tsx"),22  });23 24  settings = $page({25    path: "/settings",26    parent: this.layout,27    lazy: () => import("./Settings.tsx"),28  });29}

<NestedView /> renders the matched child page. It supports an optional errorBoundary prop.

#Error Handling

Use errorHandler to catch loader or rendering errors. Return a ReactNode for a custom error page, a Redirection to redirect, or undefined to let the error propagate to parent pages.

typescript
 1import { Redirection } from "alepha/react/router"; 2  3errorHandler: (error) => { 4  if (HttpError.is(error, 404)) { 5    return <NotFound />; 6  } 7  if (HttpError.is(error, 401)) { 8    return new Redirection("/login"); 9  }10}

#Lifecycle Callbacks

  • onEnter -- called when the user enters the page (browser only)
  • onLeave -- called when the user leaves the page (browser only)
typescript
1onEnter: () => {2  analytics.trackPageView("/dashboard");3  window.scrollTo(0, 0);4}
  • onServerResponse -- called before the server sends the response (server only)

#Page Animations

CSS-based enter/exit animations (experimental).

typescript
 1// Simple animation name 2animation: "fadeIn" 3  4// Detailed enter/exit 5animation: { 6  enter: { name: "fadeIn", duration: 300 }, 7  exit: { name: "fadeOut", duration: 200, timing: "ease-in-out" }, 8} 9 10// Dynamic based on router state11animation: (state) => ({12  enter: "slideIn",13  exit: "slideOut",14})

Define the keyframes in your CSS:

css
1@keyframes fadeIn {2  from { opacity: 0; }3  to { opacity: 1; }4}

#Router Hooks

#useRouter

Access the router for navigation. Accepts a type parameter for type-safe page name references.

typescript
 1import { useRouter } from "alepha/react/router"; 2  3function Nav() { 4  const router = useRouter<AppRouter>(); 5  6  return ( 7    <div> 8      <p>Current path: {router.pathname}</p> 9      <button onClick={() => router.push("/about")}>About</button>10      <button onClick={() => router.push("home")}>Home (by name)</button>11      <button onClick={() => router.back()}>Back</button>12      <button onClick={() => router.forward()}>Forward</button>13      <button onClick={() => router.reload()}>Reload</button>14    </div>15  );16}

Key methods and properties:

Method/Property Description
push(path, opts) Navigate to a path or page name. Options: replace, params, query, force.
back() Go back in history.
forward() Go forward in history.
reload() Reload the current page.
isActive(href) Check if the given path is the current route.
pathname Current pathname string.
query Current query parameters as Record<string, string>.
path(name, cfg) Resolve a page name to its URL path.
anchor(path) Returns { href, onClick } props for anchor elements.
setQueryParams(record) Update URL query parameters without navigation.

#useActive

Determine if a route is active and get anchor props for navigation links.

typescript
 1import { useActive } from "alepha/react/router"; 2  3function NavLink({ href, label }: { href: string; label: string }) { 4  const { isActive, isPending, anchorProps } = useActive(href); 5  6  return ( 7    <a {...anchorProps} className={isActive ? "active" : ""}> 8      {isPending ? "Loading..." : label} 9    </a>10  );11}

Accepts a string or an options object:

typescript
1const { isActive } = useActive({ href: "/docs", startWith: true });2// isActive is true for /docs, /docs/intro, /docs/api, etc.

#useQueryParams

Manage typed query parameters with a schema.

typescript
 1import { useQueryParams } from "alepha/react/router"; 2import { t } from "alepha"; 3  4function SearchPage() { 5  const [params, setParams] = useQueryParams( 6    t.object({ 7      search: t.optional(t.text()), 8      page: t.optional(t.integer()), 9    }),10  );11 12  return (13    <input14      value={params.search ?? ""}15      onChange={(e) => setParams({ ...params, search: e.target.value })}16    />17  );18}

Options:

Option Type Default Description
key string "q" Query parameter key in the URL.
format string "base64" Encoding format.
push boolean false Push to history instead of replace.

#Link Component

A convenience component for client-side navigation links.

typescript
1import { Link } from "alepha/react/router";2 3<Link href="/about">About</Link>

Wraps a standard <a> element with onClick handling via the router.

#Router Events

Route transitions emit events on the Alepha event system:

  • react:transition:begin -- navigation started (includes previous and new state)
  • react:transition:success -- navigation completed
  • react:transition:error -- navigation failed
  • react:transition:end -- always emitted after transition completes