Router
Signal-based navigation with route matching, links, and SSR-aware initialization.
Xote includes a client-side router built on signals. Route changes become regular reactive updates, so route matching and UI rendering fit the same model as the rest of the library.
Getting Started
Quick Start
Initialize the router once, then render routes inside your app.
1open Xote23Router.init()45let app = () => {6 <div>7 {Router.routes([8 {pattern: "/", render: _ => <HomePage />},9 {pattern: "/users/:id", render: params =>10 switch params->Dict.get("id") {11 | Some(id) => <UserPage id />12 | None => <NotFoundPage />13 }14 },15 ])}16 </div>17}Reading the Current Location
Use Router.location() to get the location signal, then read it like any other signal.
1Effect.run(() => {2 let current = Signal.get(Router.location())3 Console.log2("Current path:", current.pathname)4 None5})The location record contains pathname, search, and hash.
Route Patterns
Patterns can be static or dynamic. Dynamic segments use :name and are exposed through the params dictionary.
1{pattern: "/", render: _ => <HomePage />}2{pattern: "/about", render: _ => <AboutPage />}3{pattern: "/users/:id", render: params =>4 switch params->Dict.get("id") {5 | Some(id) => <UserPage id />6 | None => <NotFoundPage />7 }8}Navigation Methods
Use Router.push to create a new history entry and Router.replace to replace the current one. Both support optional ~search and ~hash.
1Router.push("/users/123", ())2Router.push("/search", ~search="?q=xote", ())3Router.replace("/login", ())Navigation Links
Use Router.link in the function API or Router.Link in JSX. Both intercept navigation without reloading the page.
1let nav = () => {2 <nav>3 <Router.Link to="/" class="nav-link">4 {Node.text("Home")}5 </Router.Link>6 <Router.Link to="/docs" class="nav-link">7 {Node.text("Docs")}8 </Router.Link>9 </nav>10}Server Rendering
On the server, initialize router state with Router.initSSR instead of Router.init. That sets the initial location without touching browser APIs.
1Router.initSSR(2 ~pathname="/docs",3 ~search="?tab=signals",4 (),5)In Practice
Complete Example
1open Xote23Router.init()45let nav = () => {6 <nav>7 <Router.Link to="/"> {Node.text("Home")} </Router.Link>8 <Router.Link to="/users"> {Node.text("Users")} </Router.Link>9 </nav>10}1112let app = () => {13 <div>14 <nav />15 {Router.routes([16 {pattern: "/", render: _ => <HomePage />},17 {pattern: "/users", render: _ => <UsersPage />},18 {pattern: "/users/:id", render: params =>19 switch params->Dict.get("id") {20 | Some(id) => <UserPage id />21 | None => <NotFoundPage />22 }23 },24 ])}25 </div>26}Working Style
Best Practices
- Initialize the router once at the app boundary, not from leaf components.
- Treat
Router.location()like shared state. Read it where needed instead of mirroring it elsewhere. - Prefer
Router.Linkfor ordinary UI navigation so the intent stays obvious in markup. - Use
Router.initSSRon the server so routing stays consistent across SSR and hydration.
Next Steps
- Pair this with Components when you want to turn routes into real pages and layouts.
- Read Server-Side Rendering if the same routes also need SSR and hydration.