Skip to content

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 Xote
2
3Router.init()
4
5let 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 None
5})

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}

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", ())

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 Xote
2
3Router.init()
4
5let 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}
11
12let 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.Link for ordinary UI navigation so the intent stays obvious in markup.
  • Use Router.initSSR on the server so routing stays consistent across SSR and hydration.

Next Steps