@isorouter/core
The framework-agnostic core: matcher, guards, lazy loading and the async-commit state machine. Zero runtime dependencies.
npm install @isorouter/corecreateCoreRouter(routes, options?)
Creates a router. A thin wrapper around new Router(routes, options).
const router = createCoreRouter([
{ path: "/", component: Home },
{ path: "/concerts/:city", component: Concerts },
{ path: "/users/:id", component: lazy(() => import("./User")) },
] as const);Declare routes as const to unlock type-safe navigation.
The external-store contract
The router publishes state as an immutable snapshot — a fresh object reference on every commit, stable in between.
router.subscribe(fn)— registersfn(snapshot), returns an unsubscribe.router.getSnapshot()— the current snapshot (referentially stable until the next commit).
This is the lowest common denominator across reactivity systems: it plugs straight into React's useSyncExternalStore, Svelte 5's createSubscriber, Vue's shallowRef, or anything else that reacts to a changed reference.
Instance methods
Navigation
router.navigate("/concerts/kyiv");
router.navigate("/concerts/kyiv", { replace: true, state: { from: "search" } });
router.back();
router.forward();navigate throws if navigation is unavailable (no polyfill loaded). back and forward are no-ops in that case. See Browser support.
isActive(path, options?)
router.isActive("/concerts"); // true for "/concerts" and "/concerts/kyiv"
router.isActive("/concerts", { exact: true }); // true only for "/concerts"Lifecycle
router.start(); // begins intercepting same-origin navigations
router.stop(); // removes the listener, aborts any in-flight commitstart() is a no-op if navigation is unavailable. Adapters call start/stop for you on mount/unmount.
Types
RouteConfig
interface RouteConfig<C = unknown> {
path?: string;
index?: boolean;
component?: C | LazyComponent<C>;
beforeLoad?: BeforeLoad;
title?: string | ((ctx: GuardContext) => string);
children?: readonly RouteConfig<C>[];
}path—"users/:id"for a param,"files/*"for a catch-all splat (params["*"]gets the remaining path, decoded). Static > param > splat, regardless of declaration order; ties broken by source order.index— matches when the parent's path is matched exactly (no remaining segments).component— a value, orlazy(() => import("./Page")). Routes with nocomponentare matched (e.g. as pass-through layouts) but contribute nothing tosnapshot.components.children— nested routes. A matched parent with no matching child still resolves on its own if the path is fully consumed.title— setsdocument.titleon commit. The deepest route in the matched chain that defines atitlewins.
RouterSnapshot
interface RouterSnapshot<C> {
/** Matched chain's components, root → leaf (routes with no component removed). */
components: C[];
params: Record<string, string>;
url: URL;
status: "idle" | "navigating" | "not-found" | "error";
error: unknown;
}GuardContext & BeforeLoad
interface GuardContext {
params: Record<string, string>;
url: URL;
pathname: string;
/** Aborts when this navigation is superseded by a newer one. */
signal: AbortSignal;
navigationType: "reload" | "push" | "replace" | "traverse";
}
type BeforeLoad = (ctx: GuardContext) => Awaitable<void | boolean | string>;Return nothing/true to allow, false to block (current URL restored), or a same-origin string to redirect (replace); a cross-origin string throws (status: "error") rather than navigating. See Navigation guards.
lazy(loader)
const User = lazy(() => import("./User"));The dynamic import runs once on first match; its default export is cached for subsequent navigations. isLazy(value) narrows a value to a LazyComponent. See Lazy loading.
Options
interface RouterOptions {
scroll?: "after-transition" | "manual";
onError?: (err: unknown) => void;
onCommit?: (snapshot: RouterSnapshot<unknown>) => void;
}scroll—"after-transition"(default) restores/resets scroll once the commit settles;"manual"leaves scroll to you.onError— called with any error thrown during a guard or lazy import.onCommit— called with each committed snapshot.
Exports
createCoreRouter, Router, matchRoutes, lazy, isLazy, and the types AnyRouter, Unsubscribe, LazyComponent, Awaitable, BeforeLoad, ExtractParams, GuardContext, Href, NavTarget, NavigationKind, RouteConfig, RouteMatch, RouteTemplate, RouterOptions, RouterSnapshot.
Other targets (TypeScript)
@isorouter/core targets TypeScript ≥ 6.0, whose lib.dom.d.ts ships the Navigation API types — no extra @types package needed. On TypeScript < 6, install @types/dom-navigation. See Installation.