Routing
Router is provider-scoped state, not a global singleton. One RouterProvider owns one RouterHistory and uses Context to provide the current route context and navigation capabilities to its subtree.
To see a complete running example first, start with Routing workspace.
Three state layers
Section titled “Three state layers”The routing runtime has three layers:
| Layer | Lifetime | Contents |
|---|---|---|
RouterHistory | Follows RouterProvider | History stack, current pointer, max history length |
RouteContext | Current history entry | Current path, dynamic params, optional RouteState |
Route | Route table definition | Path pattern, component, child routes |
Navigate writes RouterHistory. Outlet reads the current RouteContext, finds the first matching Route in routes! declaration order, and passes the remaining path to a nested Outlet.
RouterProvider(history_length: n) limits history length. The default is 10; passing 0 is treated as 1, meaning only the current latest entry is retained.
Navigate
Section titled “Navigate”use_navigate() returns a Navigate handle. Common methods:
| Method | Behavior |
|---|---|
push(path) | Add a history entry and clear route state |
push_with_state(path, state) | Add a history entry with one-shot state |
replace(path) | Replace the current history entry and clear route state |
replace_with_state(path, state) | Replace the current history entry with one-shot state |
go(delta) | Move the history pointer; negative goes back, positive goes forward |
back() / forward() | Semantic shortcuts for go(-1) / go(1) |
Navigate writes to the RouterHistory inside the current RouterProvider, not to a global route singleton. One app can nest multiple RouterProviders, each with its own history.
Nested routes
Section titled “Nested routes”A parent route component can render a fixed shell and place an Outlet inside it. The path /projects/boreal enters AppShell first, then the child Outlet matches /projects/:slug.
Static routes use prefix matching with segment boundaries; dynamic params match only one segment and do not cross /. For same-prefix routes, put the more specific dynamic route first, such as /projects/:slug before /projects.
Params and state
Section titled “Params and state”Dynamic params come from the path:
If a page needs the matched route definition itself, use use_route():
Most business pages only need use_params(). use_route() is more useful for debugging, breadcrumbs, route visualizers, or your own route helper components.
RouteState comes from navigation behavior:
try_use_route_state::<T>() returns None when there is no Router context, no state, or a type mismatch. use_route_state::<T>() is the assertion-style API and panics on absence or type mismatch.
Plain push(path) and replace(path) clear the current route state; only push_with_state and replace_with_state carry state explicitly. This prevents old state from leaking into unrelated pages when leaving a detail page.
Why not Atom
Section titled “Why not Atom”Atom is process-wide state; Router is provider-scoped state. Storing the default Router in an Atom would let multiple RouterProviders pollute each other and would make testing, embedded subapps, and local navigation harder.
If you need externally controlled routing, prefer designing an explicit controlled Router API where the caller passes the current path and change callback. Only business state that truly needs to be shared across providers should go into Atom.