Router

A lightweight client-side router with dynamic parameters, wildcard routes, grouped prefixes, middleware pipelines, navigation guards, and full browser history integration. Refresh-safe on any static host — no server rewrites required.

Quick Start

Register routes, add optional middleware, then call Aura.init() to start listening for URL changes.

js
import Aura from 'aurajs';

// Define routes
Aura.route('/', (ctx) => {
  console.log('Home page');
});

Aura.route('/users/:id', (ctx) => {
  console.log('User:', ctx.params.id);
});

// Add middleware
Aura.middleware((ctx) => {
  console.log('Navigating to', ctx.path);
  return true;
});

// Start the app
Aura.init();
// Optional: customize the route query param (default '_r')
// Aura.init({ routeParam: 'page' });

URL Encoding

Your app's logical path — /users/42?tab=posts — is what route(), navigate(), ctx.path, and currentPath all work with. In the browser's address bar that path is stored as a query parameter on the page you loaded:

browser URL
// Logical path your app navigates to:
/users/42?tab=posts

// What actually appears in the address bar:
/?_r=/users/42&tab=posts

This means a user who refreshes, bookmarks, or shares any deep link still hits the real file your host is serving (/index.html) — no server rewrite rules, no 404s on refresh, no hosting configuration. Works identically on GitHub Pages, S3, Vercel, Netlify, Cloudflare Pages, or any static file server.

The query parameter is _r by default. Override it via Aura.init():

js
Aura.init({ routeParam: 'page' });
// => /?page=/users/42&tab=posts

Hash fragments (#section) are preserved on the browser URL for native anchor scrolling. Your own ?foo=bar query string is merged alongside _r and parsed into ctx.query as usual — you never see the encoding yourself.

Types

RouteContext

Every route handler and middleware receives a RouteContext object.

PropertyTypeDescription
pathstringThe full matched path including query string.
paramsRecord<string, string>Dynamic route parameters (e.g. :id).
queryRecord<string, string>Parsed query-string key/value pairs.
namestring | undefinedOptional route name, if one was provided.

route() core

Register a route with a path pattern and a handler function. Supports dynamic segments (:param) and wildcard catch-all (/*).

Aura.route(path: string, handler: RouteHandler, name?: string): void
ParamTypeDescription
pathstringURL pattern. Use :param for dynamic segments, /* for catch-all.
handlerRouteHandlerCallback (ctx: RouteContext) => void | Promise<void> invoked when the path matches.
namestringOptional name used with urlFor() for reverse-routing.
js
// Static route
Aura.route('/about', (ctx) => {
  document.querySelector('#app').innerHTML = '<h1>About</h1>';
});

// Dynamic parameter
Aura.route('/posts/:slug', (ctx) => {
  console.log(ctx.params.slug);
});

// Named route
Aura.route('/users/:id', showUser, 'user-detail');

// Wildcard catch-all
Aura.route('/files/*', (ctx) => {
  console.log(ctx.params.wildcard); // everything after /files/
});

group() organization

Group multiple routes under a shared URL prefix. The callback receives a scoped router object with a route() method that automatically prepends the prefix.

Aura.group(prefix: string, fn: (r: { route: (path: string, handler: RouteHandler, name?: string) => void }) => void): void
ParamTypeDescription
prefixstringURL prefix prepended to every route in the group (e.g. /admin).
fnFunctionCallback that receives a scoped router { route } for registering sub-routes.
js
Aura.group('/admin', (r) => {
  // Registers /admin/dashboard
  r.route('/dashboard', (ctx) => {
    console.log('Admin dashboard');
  });

  // Registers /admin/users/:id
  r.route('/users/:id', (ctx) => {
    console.log('Admin user', ctx.params.id);
  }, 'admin-user');
});

Programmatically navigate to a path. Pushes a new entry onto the browser history stack. Respects the beforeLeave guard and runs all registered middleware before executing the route handler.

Aura.navigate(path: string): Promise<void>
ParamTypeDescription
pathstringThe target URL path (e.g. /users/42).
js
// Navigate with history push
await Aura.navigate('/dashboard');

// Navigate with query params
await Aura.navigate('/search?q=aura&page=1');

redirect() core

Navigate to a path without pushing a new browser history entry. Useful for post-login redirects or replacing the current page silently.

Aura.redirect(path: string): Promise<void>
ParamTypeDescription
pathstringThe target URL path to redirect to.
js
// Redirect without adding to history
await Aura.redirect('/login');

middleware() guard

Register a global middleware function. Middleware runs in registration order before every route handler. Return true to continue or false to abort navigation (sets page state to 'error').

Aura.middleware(fn: (ctx: RouteContext) => boolean | Promise<boolean>): void
ParamTypeDescription
fnMiddlewareFunction receiving a RouteContext. Return false to block navigation.
js
// Auth guard middleware
Aura.middleware((ctx) => {
  if (ctx.path.startsWith('/admin') && !isLoggedIn()) {
    Aura.redirect('/login');
    return false;
  }
  return true;
});

// Logging middleware
Aura.middleware((ctx) => {
  console.log(`[Router]`, ctx.path);
  return true;
});

beforeLeave() guard

Register a navigation guard that fires before leaving the current route. Only applies when using navigate(). Return false to cancel navigation (e.g. for unsaved-changes prompts).

Aura.beforeLeave(fn: (from: string, to: string) => boolean | Promise<boolean>): void
ParamTypeDescription
fnFunctionGuard receiving the current path (from) and the target path (to). Return false to cancel.
js
Aura.beforeLeave((from, to) => {
  if (hasUnsavedChanges) {
    return confirm('Discard unsaved changes?');
  }
  return true;
});

errorPage() error

Register a handler for unmatched routes (404 pages). Called when no registered route pattern matches the requested path.

Aura.errorPage(handler: (ctx: RouteContext) => void | Promise<void>): void
ParamTypeDescription
handlerRouteHandlerCallback invoked with a context whose params is empty and path is the unmatched URL.
js
Aura.errorPage((ctx) => {
  document.querySelector('#app').innerHTML = `
    <h1>404</h1>
    <p>Page not found: ${ctx.path}</p>
  `;
});

refresh() core

Re-run the handler for the current route without changing the URL or pushing history. Useful after data updates when you need to re-render the current page.

Aura.refresh(): Promise<void>

This method takes no parameters.

js
// Re-execute the current route handler
await Aura.refresh();

urlFor() utility

Generate a logical path from a named route. Replaces :param placeholders with the provided values. Returns null if no route with the given name exists. The returned value is the logical path (e.g. /users/42) — pass it to navigate() or use it as an <a href>; the router handles URL encoding automatically.

Aura.urlFor(name: string, params?: Record<string, string>): string | null
ParamTypeDescription
namestringThe name assigned when the route was registered.
paramsRecord<string, string>Optional map of parameter names to values to interpolate into the path.
js
// Register a named route
Aura.route('/users/:id/posts/:postId', handler, 'user-post');

// Generate the URL
const url = Aura.urlFor('user-post', { id: '42', postId: '7' });
// => "/users/42/posts/7"

// Unknown name returns null
Aura.urlFor('nope'); // => null

back() utility

Navigate back one entry in the browser history. Equivalent to history.back().

Aura.back(): void

This method takes no parameters.

js
Aura.back();

forward() utility

Navigate forward one entry in the browser history. Equivalent to history.forward().

Aura.forward(): void

This method takes no parameters.

js
Aura.forward();

pageState getter

Read-only getter returning the current page lifecycle state. The router automatically transitions through these states during navigation.

Aura.pageState: 'idle' | 'loading' | 'loaded' | 'error'
ValueMeaning
'idle'No navigation has occurred yet.
'loading'A route handler is currently executing.
'loaded'The route handler completed successfully.
'error'The handler threw, middleware blocked, or no route matched (404).
js
if (Aura.pageState === 'loading') {
  showSpinner();
}

currentPath getter

Read-only getter returning the full path (including query string) of the most recently handled route.

Aura.currentPath: string
js
console.log(Aura.currentPath); // e.g. "/users/42?tab=posts"

Read-only getter returning a copy of the internal navigation history array. Each entry records the path, extracted params, and a timestamp.

Aura.navigationHistory: HistoryEntry[]

HistoryEntry

PropertyTypeDescription
pathstringThe full navigated path.
paramsRecord<string, string>Extracted route parameters.
timestampnumberUnix timestamp (ms) of when the navigation occurred.
js
const history = Aura.navigationHistory;
console.log('Pages visited:', history.length);
console.log('Last page:', history[history.length - 1].path);

Emitted Events

The router emits the following events via the Aura event bus. Subscribe with Aura.on().

EventPayloadDescription
route:changeRouteContextFired after a route handler completes successfully.
route:error{ path, error }Fired when a route handler throws an exception.
route:notFound{ path }Fired when no registered route matches the path (404).
route:stateChangePageStateFired whenever the page state transitions.
js
Aura.on('route:change', (ctx) => {
  console.log('Navigated to', ctx.path);
});

Aura.on('route:notFound', ({ path }) => {
  console.log('404:', path);
});