@zoijs/router
A tiny client-side router for single-page apps. Routes are a plain object; links are plain anchors.
npm install @zoijs/core @zoijs/routerExample#
import { html, mount } from "@zoijs/core";
import { createRouter } from "@zoijs/router";
const router = createRouter({
"/": Home,
"/about": About,
"/users/:id": (params) => html`<h1>User ${params.id}</h1>`,
"*": () => html`<h1>Not Found</h1>`,
});
function App() {
return html`
<nav>${router.link("/", "Home")} ${router.link("/about", "About")}</nav>
${router.view()}
`;
}
mount(App, "#app");A route is pattern: component. A component is a function that returns an html template and receives the matched params.
API#
| Member | What it does |
|---|---|
createRouter(routes, { base, decodeSlash }) | Build a router from a { pattern: component } map |
router.view() | Render the current page — place it once |
router.link(path, text) | An <a> that navigates without a reload |
router.go(path) | Navigate from code — app paths only ("/x", "?q", "#h"); an absolute URL or scheme throws a clear TypeError |
router.path() | The current path (reactive) |
router.query() | The query string as an object (reactive) |
router.match(path?) | Resolve a path to { component, params } without rendering (for routed SSR) |
createRouter(routes, { location }) | Server-only: render the request's route under @zoijs/ssr |
Patterns support static segments, dynamic :params, and a "*" not-found route. Static routes win over param routes. The active link gets aria-current="page" automatically.
Route params are data#
Params come from the URL, so treat them as untrusted input. They're decoded per segment, so by default an encoded slash becomes a real one (/files/..%2Fadmin → "../admin"). When params feed paths, API URLs or identifiers, keep slashes encoded:
const router = createRouter(routes, { decodeSlash: false });
// "/files/..%2Fadmin" → params.name === "..%2Fadmin" (other escapes still decode)Validate and encode params before using them in a path, URL or permission check, and authorize on the server — see Production Security.
Leaving the app#
router.go() navigates within the app, so it only takes app paths. To go to another site, use a plain <a href> or location.assign(url).
Hosting under a sub-path#
If your app lives under a sub-path, pass a base so route patterns stay clean:
createRouter(routes, { base: "/app" });Known limitations#
- History mode only (deep-link refreshes need a server SPA fallback).
- No nested-outlet system, route guards, or automatic data loaders — by design.
- SSR-safe and routed: under @zoijs/ssr
createRouterandview()work without a browser. Pass the request URL as{ location }and the server renders that route; usematch()to load its data first. The client takes over on hydration.