Routing workspace
The previous tutorials each had one main component. This example moves into application structure: an outer shell stays mounted, the content on the right changes through Outlet based on the current path, and pages write to local history through Navigate.

This recording is generated by docs/tapes/router.tape and runs examples/routing/router.rs. It enters the project list, opens a dynamic detail page, then goes back and forward through history.
Run it
Section titled “Run it”Press 1, 2, and 3 to switch pages. In the project list, press j/k to move and Enter to open a detail page. Press b/f to go back and forward, r to replace the current history entry with the home page, and q to exit.
Route tree
Section titled “Route tree”Route declaration
Section titled “Route declaration”routes! uses declarative syntax close to element!. The parent route renders the shell; child routes are rendered by the Outlet inside the shell.
Declare dynamic routes before static prefix routes. /projects/:slug should come before /projects; otherwise /projects/boreal may match the /projects prefix first.
Shell stays mounted
Section titled “Shell stays mounted”AppShell registers global navigation keys and places an Outlet on the right. When the path changes, the shell does not need to unmount; only the page inside Outlet changes. The key structure is shown below with style and navigation list details omitted.
Here push adds a history entry, replace overwrites the current entry, and back / forward only move the current pointer. The history lifetime follows RouterProvider; it is not process-wide global state.
Dynamic params and state
Section titled “Dynamic params and state”The project list page writes the selected project into the path and carries one navigation state payload with push_with_state:
The detail page reads slug from the path, then tries to read the RouteState carried by this navigation:
RouteState is “context attached to this navigation.” It is useful for carrying selection source, temporary notices, or lightweight payloads when moving from list to detail. It is not a global store and should not replace URL params.
Why state is absent by default
Section titled “Why state is absent by default”navigate.push(path) and navigate.replace(path) clear the current route state. Only push_with_state and replace_with_state carry state explicitly.
That semantic is important: when moving from a detail page with state to an ordinary page, old state should not leak forward. Route state should be intentionally carried like a navigation parameter, not implicitly inherited from the previous page.
Next, read the routing core model to break down the boundaries between RouterHistory, RouteContext, and Route.