Advanced Routing SPA+

Centralized configuration, query parameters, 404 handling, and URL normalization. For basic routing setup, see Route Management.

Centralized Configuration

Registering routes one at a time works fine for a handful of pages. As an app grows, a single configuration keeps every path, guard, and redirect visible in one place, the same role Symfony's routes.yaml plays:

Constructor Config

const router = wildflower.createRouter({
    mode: 'history',
    routes: [
        { path: '/', name: 'home', handler: showHome },
        { path: '/about', name: 'about', handler: showAbout },
        {
            path: '/admin',
            name: 'admin',
            meta: { requiresAuth: true },
            beforeEnter: checkAuth,
            handler: showAdmin
        },
        {
            path: '/docs/:section?',
            name: 'docs',
            defaults: { section: 'introduction' },
            handler: showDocs
        }
    ]
});

router.init();

Dynamic Loading

// Load routes from array
router.loadRoutes([
    { path: '/users', name: 'users', handler: showUsers },
    { path: '/users/:id', name: 'user', handler: showUser }
]);

// Or fetch from server
fetch('/api/routes.json')
    .then(r => r.json())
    .then(routes => router.loadRoutes(routes));

Route Config Options

Option Type Description
pathstringURL pattern (required)
namestringRoute name for programmatic navigation
handlerfunctionRoute handler function
metaobjectCustom metadata (auth flags, titles, etc.)
beforeEnterfunctionPer-route entry guard
beforeLeavefunctionPer-route leave guard
redirectstringRedirect target path
defaultsobjectDefault values for optional parameters
aliasstring/arrayAlternative paths for this route
transitionstring/falseView transition type or disable
childrenarrayNested child routes

Lazy Route Components

A route's component may be a function instead of a name. The router calls it at navigation time and awaits the result, which is how a route's code loads on demand:

const router = wildflower.createRouter({
    routes: [
        { path: '/reports', component: () => import('/js/pages/reports.js') }
    ],
    loadingTimeout: 8000,
    onLoadingStart(to) { /* show a route-level spinner */ },
    onLoadingEnd(to)   { /* hide it */ }
});

The onLoadingStart and onLoadingEnd callbacks bracket the load, and loadingTimeout bounds how long the spinner logic waits before being told the load is slow; the load itself continues. A component that fails to load raises WF-710 naming the route, which usually means the network request failed or the module path is wrong.

Query Parameter Arrays

The router automatically parses duplicate query keys into arrays:

// URL: /search?tags=javascript&tags=css&tags=html
router.onRoute('/search', {
    handler: ({ query }) => {
        console.log(query.tags);
        // ['javascript', 'css', 'html']
    }
});

// Also supports bracket notation: /search?tags[]=a&tags[]=b

// Navigate with array query params
router.navigate('/search', {
    query: { tags: ['javascript', 'css'], page: 1 }
});
// Produces: /search?tags=javascript&tags=css&page=1
Automatic Detection: A single value stays as a string. When the same key appears more than once, the router converts it to an array automatically.

404 Handling

When no route matches, the router dispatches a route:notFound event and optionally falls back to a default route:

// Listen for 404 events
document.addEventListener('route:notFound', (e) => {
    console.log('Page not found:', e.detail.path);
    console.log('Query params:', e.detail.query);
    showNotFoundPage();
});

// Configure a default fallback route
const router = wildflower.createRouter({
    defaultRoute: '/',  // Redirect here when no route matches
    routes: [
        { path: '/', handler: showHome },
        { path: '/about', handler: showAbout }
    ]
});

Event Detail

Property Type Description
pathstringThe unmatched path
queryobjectParsed query parameters

Trailing Slash Normalization

The router automatically normalizes trailing slashes so /about/ matches a route defined as /about:

router.onRoute('/about', {
    handler: () => showAbout()
});

// All of these match the /about route:
router.navigate('/about');    // exact match
router.navigate('/about/');   // trailing slash stripped automatically

// The root path '/' is never stripped
Automatic: No configuration needed. Trailing slashes are stripped before route matching for all paths except the root /.
SPA E-commerce Store

Product detail routes, a cart that survives navigation, and a multi-step checkout.