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 |
|---|---|---|
path | string | URL pattern (required) |
name | string | Route name for programmatic navigation |
handler | function | Route handler function |
meta | object | Custom metadata (auth flags, titles, etc.) |
beforeEnter | function | Per-route entry guard |
beforeLeave | function | Per-route leave guard |
redirect | string | Redirect target path |
defaults | object | Default values for optional parameters |
alias | string/array | Alternative paths for this route |
transition | string/false | View transition type or disable |
children | array | Nested 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
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 |
|---|---|---|
path | string | The unmatched path |
query | object | Parsed 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
/.
Product detail routes, a cart that survives navigation, and a multi-step checkout.