URL to application intent

white-label-router

Use one route contract across browser and server runtimes while preserving real links, scoped progressive enhancement, URL semantics, History API behavior, and requestable server routes.

Install and requirements

  • Node.js ^22.18.0 or >=24.11.0
  • Browser History API/location/DOM events only for browser enhancement
  • Directly requestable server routes for public browser URLs
import Router from 'white-label-router';
const router = new Router();

Route definitions and matching

Routes match complete path boundaries. /products matches /products and /products/42 but not /products-old. When more than one own route prefix matches, Router selects the longest matching prefix so the most specific route wins regardless of declaration order.

A route can be a function or an object with title, focus, secure, and view lifecycle fields. secure() must return exactly true to allow navigation. Router pairs a successfully initialized object-view lifecycle with the exact destroy callback that owned it, even if route configuration is later replaced or mutated. defaultRoute runs when no configured prefix matches.

router.routes = {
    '/products': productsRoute,
    '/products/sale': saleRoute,
    defaultRoute
};

Progressive enhancement

Browser navigation should start with a real anchor. data-pushstate opts eligible same-origin links into client-side navigation. Modified clicks, non-left clicks, downloads, alternate targets, and cross-origin URLs stay native.

By default opted-in clicks are observed across document. Set navigationRoot to a Document or Element to scope interception to an owned region; reassigning it while active moves the click listener, and null restores document-wide behavior. navigationRoot is separate from callback scope.

Initialization dispatches the current URL without creating a duplicate history entry. Back/forward navigation reads the authoritative window.location rather than trusting arbitrary history state.

const router = new Router();
router.navigationRoot = document.querySelector('[data-product-refinements]');
router.initialize();

Location payload

Route callbacks receive scope and a location object. data.url contains decoded path segments after the matched prefix, data.query contains the last value for each query key, and data.mediator contains programmatic navigation metadata or router:navigate CustomEvent.detail.

Applications remain responsible for validating decoded URL values before using them as trusted domain data.

{
  url: '/products/42?color=blue',
  data: {
    url: ['42'],
    query: {color: 'blue'},
    mediator: {source: 'featured'}
  }
}

Route lifecycle, title, and focus

Router validates the destination guard and runnable view behavior before tearing down the current route. On a successful route change, the previous route view's destroy() runs before the next view's initialize(). Browser object routes can update document title and focus after rendering.

Rejected navigation preserves the last successful URL, route/location state, current route lifecycle, and browser history. The default focus selector is main h1; set focus: false when navigation represents an in-page refinement that should preserve focus. Server runtimes keep pageTitle state but do not mutate a document or focus target.

Serverless routing

Pass the incoming request URL explicitly to Router in a serverless function. The server path uses the same route matching, query parsing, guards, and lifecycle contract without requiring window or document.

Scope mutable Router state to the request unless sharing is deliberate. Router handles application routing inside an invocation; it does not replace API Gateway, platform routes, CDN rules, or other cloud request routing.

const router = new Router();
router.routes = routes;
router.navigate(new URL(request.url).pathname);

Configuration properties

router.routes

Route table containing route functions or lifecycle route objects; the longest matching own path prefix wins regardless of insertion order.

Returns
Configuration property.
Runtime
Browser + Node.js

router.scope

Application Element passed to callbacks; null is valid on server paths.

Returns
Configuration property.
Runtime
Browser + Node.js

router.navigationRoot

Optional Document or Element that scopes data-pushstate click interception; null uses document. Changing it while listeners are active moves the owned click listener.

Returns
Configuration property.
Runtime
Browser

router.mediator

Optional EventTarget-compatible source for router:navigate CustomEvents; an active subscription moves from the previous mediator when this property changes.

Returns
Configuration property.
Runtime
Browser + Node.js

route.title

Document title applied after browser routing; retained as pageTitle state on servers.

Returns
Route configuration.
Runtime
Browser + Node.js

route.focus

Selector to focus after browser navigation; defaults to main h1, false preserves focus.

Returns
Route configuration.
Runtime
Browser

route.secure(scope, location)

Navigation guard. Only an exact true result allows the route.

Returns
unknown; navigation accepts only true.
Runtime
Browser + Node.js

route.view

Route function or lifecycle object with initialize() and optional destroy().

Returns
Route configuration.
Runtime
Browser + Node.js

Router API

new Router()

Create an independent router with default fallback behavior and isolated listener state.

Returns
A Router instance.
Runtime
Browser + Node.js

initialize(url?)

Dispatch the current browser URL or an explicit server URL and attach applicable listeners.

ParameterTypeDefaultDescription
urlstring—Optional in browsers; server applications should normally provide the request URL.
Returns
The same Router instance.
Runtime
Browser + Node.js

addListeners()

Attach browser and optional mediator handlers once.

Returns
The same Router instance.
Runtime
Browser + Node.js
  • Repeated initialization is idempotent.

destroy()

Release routing listeners owned by the instance.

Returns
The same Router instance after cleanup.
Runtime
Browser + Node.js

removeListeners()

Release listeners owned by this router; subclasses may extend this lifecycle hook.

Returns
The same Router instance.
Runtime
Browser + Node.js

eventPushStateClick(event)

Intercept an eligible same-origin data-pushstate link while preserving native browser actions that should not be enhanced.

Returns
Router instance after handled navigation; true when native/default handling should continue.
Runtime
Browser

eventPopState()

Dispatch the authoritative current browser history location without adding a new entry.

Returns
The same Router instance.
Runtime
Browser

parseQueryString(query)

Decode query parameters using URLSearchParams and keep the last duplicate value.

Returns
Plain Record<string, string>.
Runtime
Browser + Node.js

setLocationData(data?)

Rebuild decoded path/query/mediator payload for the selected route.

Returns
undefined; updates locationData in place.
Runtime
Browser + Node.js

applyPageContext(route)

Apply configured title and focus after routing when the runtime supports those effects.

Returns
The same Router instance.
Runtime
Browser + Node.js

navigate(url?, data?, isPopState?)

Atomically match a route, enforce its guard/runnable view contract, transition view lifecycle, and update browser history when applicable.

ParameterTypeDefaultDescription
urlstring—Target URL; current browser URL when omitted, / fallback outside browsers.
dataobject—Caller metadata exposed as location.data.mediator.
isPopStateboolean—Prevent creation of a new history entry for back/forward dispatch.
Returns
The same Router on success; false for malformed/wrong-type URL input, rejected guards, cross-origin browser URLs, or routes without runnable view behavior.
Runtime
Browser + Node.js
  • Rejected navigation does not destroy the current route, push browser history, or replace the last successful Router URL/route/location payload.
  • Server adapters should hand Router a fully qualified request URL. If a host exposes only a raw origin-form target, prefix it with the configured application origin without first reparsing the raw target; passing a raw //... target as a relative WHATWG URL changes its meaning.

TypeScript

Router.Route, Router.Location, Router.Navigation, and Router.Handler expose the supported public route contracts.

DOM types are present because the same class supports browser scopes/events and Document/Element navigation roots; server hosts do not need DOM globals at runtime.

Security and trust boundaries

URL and query values are decoded, not trusted. Validate them before using them for authorization, data access, or domain decisions.

route.secure is an application guard hook; Router does not implement authentication policy.

Malformed navigation candidates are rejected with false before Router state changes. Server adapters should preserve raw request-target path semantics and hand Router a fully qualified URL.

Design boundaries

Router owns URL-to-intent translation. It does not own state, rendering, data loading, authentication policy, or server infrastructure.

Real links and directly requestable server URLs remain the foundation; Router enhances them instead of replacing them. navigationRoot limits interception ownership; callback scope remains application data.