Defining Routes
Routes are defined using the createRouter function, which provides a chainable builder API for registering URL patterns, components, middleware, prefetch handlers, and more.
Basic Routes
Section titled “Basic Routes”The createRouter function takes a callback that receives a route factory. Call it with a URL pattern and chain methods to configure the route:
import { createRouter } from "@studiolambda/router/react";
const router = createRouter(function (route) { route("/").render(Home); route("/about").render(About); route("/contact").render(Contact);});Each route(pattern) call returns a builder. The .render(Component) method finalizes the route, registering it in the underlying trie-based matcher with the given component.
URL Patterns
Section titled “URL Patterns”λ Router supports three types of URL segments:
Static Segments
Section titled “Static Segments”Exact literal matches. These have the highest priority when multiple patterns could match.
route("/users/settings").render(UserSettings);route("/blog/archive").render(BlogArchive);Dynamic Segments
Section titled “Dynamic Segments”Capture a single URL segment into a named parameter using the :name syntax. Access the captured values with the useParams hook.
route("/user/:id").render(UserProfile);route("/post/:slug").render(BlogPost);route("/org/:orgId/team/:teamId").render(Team);In the component:
import { useParams } from "@studiolambda/router/react";
function UserProfile() { const { id } = useParams();
return <h1>User {id}</h1>;}Wildcard Segments
Section titled “Wildcard Segments”Capture all remaining segments using the *name syntax. Wildcards must be the last segment in a pattern. The captured value is the remaining path segments joined by /.
route("/files/*path").render(FileViewer);route("/docs/*").render(DocsPage);A bare * without a name captures into params["*"].
function FileViewer() { const { path } = useParams();
// URL: /files/images/photo.jpg // path = "images/photo.jpg"
return <div>Viewing: {path}</div>;}Matching Priority
Section titled “Matching Priority”When multiple patterns could match a URL, the trie uses this priority order at each segment level:
- Static — exact literal match (highest priority)
- Dynamic —
:paramcaptures - Wildcard —
*paramcatch-all (lowest priority)
This means /users/settings always takes precedence over /users/:id for the URL /users/settings.
Route Groups
Section titled “Route Groups”Groups let you share configuration (path prefixes, middleware, prefetch handlers) across multiple routes without repeating yourself. Call .group() on a builder to get a new route factory scoped to that configuration:
const router = createRouter(function (route) { route("/").render(Home);
// All routes under /dashboard share the prefix and middleware. const dashboard = route("/dashboard").middleware([AuthGuard]).group();
dashboard("/").render(DashboardHome); dashboard("/analytics").render(Analytics); dashboard("/settings").render(Settings);
// Nested groups inherit parent configuration. const admin = dashboard("/admin").middleware([AdminOnly]).group();
admin("/users").render(AdminUsers); admin("/logs").render(AdminLogs);});In this example:
dashboard("/analytics")registers the pattern/dashboard/analyticswithAuthGuardmiddleware.admin("/users")registers/dashboard/admin/userswith bothAuthGuardandAdminOnlymiddleware (parent middleware wraps outermost).
Groups Without Paths
Section titled “Groups Without Paths”You can call route() without a path to create a group that only shares middleware or prefetch configuration without contributing a path segment:
const router = createRouter(function (route) { // These routes all have AuthGuard but no shared path prefix. const authed = route().middleware([AuthGuard]).group();
authed("/profile").render(Profile); authed("/settings").render(Settings); authed("/billing").render(Billing);});Redirects
Section titled “Redirects”Use .redirect(target) to register a route that redirects to another URL during the precommit phase (before the URL bar updates):
const router = createRouter(function (route) { route("/old-page").redirect("/new-page");});Redirects happen at the Navigation API level — the browser never commits the original URL to the address bar. The redirect target is an absolute path.
Dynamic Redirects
Section titled “Dynamic Redirects”The .redirect() method also accepts a callback function that receives the PrefetchContext and returns the target path. This is useful when the redirect target depends on the matched route parameters or the destination URL:
const router = createRouter(function (route) { // Carry the :id param to the new URL. route("/old-user/:id").redirect(function ({ params }) { return `/user/${params.id}`; });
// Preserve search parameters across a redirect. route("/old-search").redirect(function ({ url }) { return `/search${url.search}`; });});The callback receives the same PrefetchContext object that prefetch handlers receive (params, url, and controller). The returned string is the absolute path to redirect to — it is not prefixed by parent groups.
Chaining Configuration
Section titled “Chaining Configuration”All builder methods (except .render(), .redirect(), and .group()) return the builder for chaining:
route("/dashboard") .middleware([AuthGuard]) .prefetch(loadDashboardData) .scroll("after-transition") .focusReset("after-transition") .render(Dashboard);Available Builder Methods
Section titled “Available Builder Methods”| Method | Description |
|---|---|
.middleware(list) | Appends middleware components. Inherited group middleware wraps outermost. |
.prefetch(fn) | Adds a prefetch function that runs before URL commits. Parent prefetches run first. |
.scroll(behavior) | Sets scroll restoration: "after-transition" (default) or "manual". |
.focusReset(behavior) | Sets focus reset: "after-transition" (default) or "manual". |
.formHandler(fn) | Sets a handler for POST form submissions matching this route. |
.render(Component) | Registers the route with the given component (terminal). |
.redirect(target) | Registers a redirect to the target path or callback (terminal). |
.group() | Returns a scoped route factory inheriting this builder’s config (terminal). |
Inheritance Model
Section titled “Inheritance Model”When using groups, configuration is inherited with the following rules:
- Path prefixes are concatenated:
route("/a").group()thenchild("/b")→/a/b. - Middleware is prepended: parent middleware wraps outermost, then child middleware wraps inner.
- Prefetch handlers chain sequentially: parent prefetch runs first, then child prefetch.
- Scroll, focus reset, and form handler are set per-route and not inherited.