> ## Documentation Index
> Fetch the complete documentation index at: https://juo.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Router

> In-page navigation without coupling blocks to a specific router.

The router service abstracts in-page navigation from any specific router implementation (Vue Router, React Router, Shopify navigation, plain `location`). Blocks call `push`, subscribe to navigation events, and read the list of registered pages without importing any concrete router.

Unlike the other services, the router has no built-in adapter — the host application is expected to wire one up directly.

## Setup

```ts theme={null}
import { provideContext, RouterServiceContext } from "@juo/blocks";

provideContext(root, RouterServiceContext, {
  push(to) {
    if (typeof to === "string") {
      window.location.href = to;
    } else if ("path" in to) {
      const qs = to.query ? "?" + new URLSearchParams(to.query) : "";
      window.location.href = to.path + qs;
    } else {
      // resolve named route to a path against the route table
      window.location.href = resolveNamed(to.name, to.params);
    }
  },
  subscribe(cb) {
    const handler = () => cb({ path: window.location.pathname });
    window.addEventListener("popstate", handler);
    return () => window.removeEventListener("popstate", handler);
  },
  getPages() {
    return [
      { path: "/", block: "HomePage", title: "Home" },
      { path: "/account", block: "AccountPage", title: "Account" },
    ];
  },
});
```

## Shape

```ts theme={null}
type RouterPushTarget =
  | string
  | { path: string; query?: Record<string, string> }
  | { name: string; params?: Record<string, string>; query?: Record<string, string> };

type RouterService = {
  push(to: RouterPushTarget): void;
  subscribe(callback: (event: { path: string }) => void): () => void;
  getPages(): readonly { path: string; block: string; title: string }[];
};
```

`push` accepts the same three shapes as `vue-router`'s `push` — a plain path, a path+query object, or a named-route object. The adapter decides how to resolve named routes.

## Example: navigation link block

```ts theme={null}
import { defineBlock, injectContext, RouterServiceContext } from "@juo/blocks";

defineBlock("NavLink", {
  group: "theme",
  schema: {
    type: "object",
    properties: {
      props: {
        type: "object",
        properties: { to: { type: "string" }, label: { type: "string" } },
        required: ["to", "label"],
        additionalProperties: false,
      },
    },
    required: ["props"],
    additionalProperties: false,
  } as const,
  initialValue: () => ({ props: { to: "/", label: "Home" } }),
  renderer(block) {
    const el = document.createElement("a");
    const router = injectContext(el, RouterServiceContext);

    el.href = block.props.to;
    el.textContent = block.props.label;
    el.addEventListener("click", (e) => {
      e.preventDefault();
      router.push(block.props.to);
    });

    return el;
  },
});
```

## Page registrations

`getPages()` returns the static page table — useful for blocks that render a nav menu or sitemap. Each entry maps a path to the **block name** that renders it, plus a display title.
