Skip to content

Components

λ Router provides two primary components: Router for rendering matched routes, and Link for navigation links with prefetch support and active state detection.

The Router component is the top-level entry point. It listens to the Navigation API’s navigate event, matches URLs against your routes, and renders the matched component inside a <Suspense> boundary with middleware wrapping.

import { createRouter, Router } from "@studiolambda/router/react";
function App() {
const router = createRouter(function (route) {
route("/").render(Home);
route("/about").render(About);
});
return <Router matcher={router} />;
}
PropTypeDefaultDescription
matcherMatcher<Handler>Context valueRoute matcher created by createRouter. Falls back to MatcherContext.
notFoundComponentTypeBuilt-in NotFoundComponent to render when no route matches the current URL.
fallbackReactNodeundefinedSuspense fallback shown while a lazy component is loading.
navigationNavigationwindow.navigationOverride the Navigation API object. Falls back to NavigationContext then window.navigation.
transition[boolean, StartTransitionFn]Internal useTransitionExternal transition tuple for coordinating with other parts of your app.
onNavigateSuccess() => voidundefinedCallback fired after a successful navigation.
onNavigateError(error: unknown) => voidundefinedCallback fired when a navigation fails.

The Router wraps the matched component in a <Suspense> boundary. If the component suspends (e.g., while loading data with use()), the fallback prop determines what the user sees:

<Router matcher={router} fallback={<LoadingSpinner />} />

Because navigations are wrapped in startTransition, React keeps the previous route visible while the new one is loading. The Suspense fallback is only shown for the initial render or when there is no previous UI to hold onto.

When no route matches the current URL, the notFound component renders:

function Custom404() {
return (
<div>
<h1>Page not found</h1>
<a href="/">Go home</a>
</div>
);
}
<Router matcher={router} notFound={Custom404} />;

The Router makes several context values available to all descendant components:

ContextHookDescription
TransitionContextuseIsPending()[isPending, startTransition] for navigation transitions.
NavigationContextuseNavigation()The native Navigation object.
MatcherContextThe route matcher instance.
NavigationTypeContextuseNavigationType()"push", "replace", "reload", "traverse", or null.
NavigationSignalContextuseNavigationSignal()AbortSignal from the current navigation, or null.
PathnameContextusePathname()Current URL pathname string.
ParamsContextuseParams()Route parameters from the matched pattern.

The Link component renders an <a> element with router-integrated prefetch and active link detection. The Navigation API intercepts link clicks natively — no onClick or preventDefault is needed.

import { Link } from "@studiolambda/router/react";
function Nav() {
return (
<nav>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
<Link href="/contact">Contact</Link>
</nav>
);
}

Link extends all standard <a> element attributes with additional router-specific props:

PropTypeDefaultDescription
hrefstringThe destination URL.
prefetch"hover" | "viewport"undefinedWhen to trigger the route’s prefetch handler.
oncebooleantrueOnly prefetch once per link instance.
matcherMatcher<Handler>Context valueOverride the route matcher for prefetch resolution.
classNamestring | ((props: LinkRenderProps) => string)Static class string or a function receiving { isActive }.
activeExactbooleantruetrue for exact match, false for prefix match.

Links automatically detect whether they match the current URL and apply attributes accordingly:

  • data-active is set when the link is active.
  • aria-current="page" is set for accessibility.

Use the className function prop for dynamic styling based on active state:

<Link
href="/about"
className={({ isActive }) => (isActive ? "nav-link active" : "nav-link")}
>
About
</Link>

By default, activeExact is true, meaning /about only matches the exact pathname /about. Set activeExact={false} for prefix matching, where /about would also match /about/team:

<Link href="/docs" activeExact={false}>
Documentation
</Link>

The prefetch prop controls when the link triggers the route’s prefetch handler:

  • "hover" — Prefetch when the user hovers over the link (mouseenter).
  • "viewport" — Prefetch when the link enters the viewport (IntersectionObserver).
<Link href="/dashboard" prefetch="hover">
Dashboard
</Link>
<Link href="/heavy-page" prefetch="viewport">
Heavy Page
</Link>

When prefetching from a link, the prefetch handler receives a stub NavigationPrecommitController (no-op redirect) since there is no real navigation event happening yet. The params and url in the PrefetchContext are still fully resolved from the matched route.

The once prop (default true) ensures the prefetch handler only runs once per link instance, preventing redundant data loading on repeated hovers.

See the Prefetching guide for more details on writing prefetch handlers.