Skip to main content
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

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

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.
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.