Router configuration
Learn about configuring the Router.
The router allows you to configure how it interprets and handles routing in your Aurelia applications. The customize method on the RouterConfiguration object can be used to configure router settings.
Complete Configuration Reference
The router accepts the following configuration options through RouterConfiguration.customize() (all map directly to RouterOptions except for basePath):
useUrlFragmentHash
boolean
false
When true, uses hash (#/path) URLs instead of pushState. Leave false for clean URLs.
useHref
boolean
true
Enables the router to intercept standard href links. Set to false if you only want to route via the load attribute.
historyStrategy
'push' | 'replace' | 'none' | (instructions) => HistoryStrategy
'push'
Controls how each navigation interacts with history. Provide a function to choose per navigation.
basePath
string | null
null
Overrides the base segment used to resolve relative routes. Defaults to document.baseURI.
activeClass
string | null
null
CSS class applied by the load attribute when a link is active.
useNavigationModel
boolean
true
Generates the navigation model so you can build menus from IRouter.navigation.
buildTitle
(transition: Transition) => string | null
null
Customises how page titles are produced. Return null to skip title updates.
restorePreviousRouteTreeOnError
boolean
true
Restores the previous route tree if a navigation throws, preventing partial states.
treatQueryAsParameters
boolean
false (deprecated)
Treats query parameters as route parameters. Avoid new usage; scheduled for removal in the next major release.
Pass a partial options object—the router merges your values with the defaults so you only specify what changes. If you need to adjust settings later, resolve
IRouterOptionsfrom DI and mutate it at runtime. UnlikeRouterConfiguration.customizein@aurelia/router-direct, this overload does not accept a callback—use app tasks or register your own startup code if you need to deferrouter.start().
Choose between hash and pushState routing using useUrlFragmentHash
useUrlFragmentHashIf you do not provide any configuration value, the default is pushState routing. If you prefer hash-based routing to be used, you can enable this like so:
import Aurelia from 'aurelia';
import { RouterConfiguration } from '@aurelia/router';
import { MyApp } from './my-app';
Aurelia
.register(RouterConfiguration.customize({ useUrlFragmentHash: true }))
.app(MyApp)
.start();By calling the customize method, you can supply a configuration object containing the property useUrlFragmentHash and supplying a boolean value. If you supply true this will enable hash mode. The default is false.
If you are working with pushState routing, you will need a <base> element with href attribute (for more information, refer MDN) in the head of your document. The scaffolded application from the CLI includes this in the index.html file, but if you're starting from scratch or building within an existing application you need to be aware of this.
<head>
<base href="/">
</head>Configuring basePath
basePathConfiguring a base path is useful in many real-life scenarios. One such example is when you are hosting multiple smaller application under a single hosting service. In this case, you probably want the URLs to look like https://example.com/app1/view42 or https://example.com/app2/view21. In such cases, it is useful to specify a different base#href value for every app.
<!-- app1/index.html -->
<head>
<base href="/app1">
</head>
<!-- app2/index.html -->
<head>
<base href="/app2">
</head>Run the following example to understand how the value defined in base#href is affecting the URLs.
When you open the example in a new browser tab, you can note that the URL in the address bar looks the HOSTING_PREFIX/app/home or HOSTING_PREFIX/app/about. This is also true for the href values in the a tags. This happens because <base href="/app"> is used in the index.ejs (producing the index.html). In this case, the router is picking up the baseURI information and performing the routing accordingly.
This needs bit more work when you are supporting multi-tenancy for your app. In this case, you might want the URLs look like https://example.com/tenant-foo/app1/view42 or https://example.com/tenant-bar/app2/view21. You cannot set the document.baseURI every time you start the app for a different tenant, as that value is static and readonly, read from the base#href value.
With router you can support this by setting the basePath value differently for each tenant, while customizing the router configuration, at bootstrapping phase. Following is an example that implements the aforementioned URL convention. To better understand, open the the example in a new tab and check the URL in address bar when you switch tenants as well as the links in the a tags.
The actual configuration takes place in the main.ts while customizing the router configuration in the following lines of code.
// this can either be '/', '/app[/+]', or '/TENANT_NAME/app[/+]'
let basePath = location.pathname;
const tenant =
(!basePath.startsWith('/app') && basePath != '/'
? basePath.split('/')[1]
: null) ?? 'none';
if (tenant === 'none') {
basePath = '/app';
}
const host = document.querySelector<HTMLElement>('app');
const au = new Aurelia();
au.register(
StandardConfiguration,
RouterConfiguration.customize({
basePath,
}),
Registration.instance(ITenant, tenant) // <-- this is just to inject the tenant name in the `my-app.ts`
);There are also the following links, included in the my-app.html, to simulate tenant switch/selection.
tenant: ${tenant}
<nav>
<a href="${baseUrl}/foo/app" external>Switch to tenant foo</a>
<a href="${baseUrl}/bar/app" external>Switch to tenant bar</a>
</nav>
<nav>
<a load="home">Home</a>
<a load="about">About</a>
</nav>
<au-viewport></au-viewport>
import { customElement } from '@aurelia/runtime-html';
import { route } from '@aurelia/router';
import template from './my-app.html';
import { Home } from './home';
import { About } from './about';
import { DI } from '@aurelia/kernel';
export const ITenant = DI.createInterface<string>('tenant');
@route({
routes: [
{
path: ['', 'home'],
component: Home,
title: 'Home',
},
{
path: 'about',
component: About,
title: 'About',
},
],
})
@customElement({ name: 'my-app', template })
export class MyApp {
private baseUrl = location.origin;
private readonly tenant: string = resolve(ITenant);
}Note the a tags with external attribute. Note that when you switch to a tenant, the links in the a tags also now includes the tenant name; for example when we switch to tenant 'foo' the 'Home' link is changed to /foo/app/home from /app/home.
Customizing title
A buildTitle function can be used to customize the default behavior of building the title. For this example, we assume that we have the configured the routes as follows:
import { route, IRouteViewModel } from '@aurelia/router';
@route({
title: 'Aurelia', // <-- this is the base title
routes: [
{
path: ['', 'home'],
component: import('./components/home-page'),
title: 'Home',
}
]
})
export class MyApp implements IRouteViewModel {}With this route configuration in place, when we navigate to /home, the default-built title will be Home | Aurelia. We can use the following buildTitle function that will cause the title to be Aurelia - Home when users navigate to / or /home route.
// main.ts
import { RouterConfiguration, Transition } from '@aurelia/router';
import { Aurelia } from '@aurelia/runtime-html';
const au = new Aurelia();
au.register(
RouterConfiguration.customize({
buildTitle(tr: Transition) {
const root = tr.routeTree.root;
const baseTitle = root.context.config.title;
const titlePart = root.children.map(c => c.title).join(' - ');
return `${baseTitle} - ${titlePart}`;
},
}),
);Check out the following live example. You might need to open the demo in a new tab to observe the title changes.
Translating the title
When localizing your app, you would also like to translate the title. Note that the router does not facilitate the translation by itself. However, there are enough hooks that can be leveraged to translate the title. To this end, we would use the data property in the route configuration to store the i18n key.
import { IRouteViewModel, Routeable } from "aurelia";
export class MyApp implements IRouteViewModel {
static title: string = 'Aurelia';
static routes: Routeable[] = [
{
path: ['', 'home'],
component: import('./components/home-page'),
title: 'Home',
data: {
i18n: 'routes.home'
}
}
];
}As data is an object of type Record<string, unknown>, you are free to chose the property names inside the data object. Here we are using the i18n property to store the i18n key for individual routes.
In the next step we make use of the buildTitle customization as well as a AppTask hook to subscribe to the locale change event.
import { I18N, Signals } from '@aurelia/i18n';
import { IEventAggregator } from '@aurelia/kernel';
import { IRouter, RouterConfiguration, Transition } from '@aurelia/router';
import { AppTask, Aurelia } from '@aurelia/runtime-html';
(async function () {
const host = document.querySelector<HTMLElement>('app');
const au = new Aurelia();
const container = au.container;
let i18n: I18N | null = null;
let router: IRouter | null = null;
au.register(
// other registrations such as the StandardRegistration, I18NRegistrations come here
RouterConfiguration.customize({
buildTitle(tr: Transition) {
// Use the I18N to translate the titles using the keys from data.i18n.
i18n ??= container.get(I18N);
const root = tr.routeTree.root;
const baseTitle = root.context.config.title;
const child = tr.routeTree.root.children[0];
return `${baseTitle} - ${i18n.tr(child.data.i18n as string)}`;
},
}),
AppTask.afterActivate(IEventAggregator, ea => {
// Ensure that the title changes whenever the locale is changed.
ea.subscribe(Signals.I18N_EA_CHANNEL, () => {
(router ??= container.get(IRouter)).updateTitle();
});
}),
);
// start aurelia here
})().catch(console.error);This customization in conjunction with the previously shown routing configuration will cause the title to be Aurelia - Startseite when user is navigated to / or /home route and the current locale is de. Here we are assuming that the i18n resource for the de locale contains the following.
{
"routes": {
"home": "Startseite"
}
}The following example demonstrate the title translation.
Enable or disable the usage of the href custom attribute using useHref
href custom attribute using useHrefBy default, the router will allow you to use both href as well as load for specifying routes. Where this can get you into trouble is external links, mailto: links and other types of links that do not route. A simple example looks like this:
<a href="mailto:[email protected]">Email Me</a>This seemingly innocent and common scenario by default will trigger the router and will cause an error.
You have two options when it comes to working with external links. You can specify the link as external using the external attribute.
<a href="mailto:[email protected]" external>Email Me</a>Or, you can set useHref to false (default is true) and only ever use the load attribute for routes.
import Aurelia from 'aurelia';
import { RouterConfiguration } from '@aurelia/router';
Aurelia
.register(RouterConfiguration.customize({
useHref: false,
}))
.app(MyApp)
.start();Configure browser history strategy
Using the historyStrategy configuration option it can be instructed, how the router should interact with the browser history object. This configuration option can take the following values: push, replace, and none.
push
pushThis is the default strategy. In this mode, the router will interact with Browser history to push a new navigation state each time a new navigation is performed. This enables the end users to use the back and forward buttons of the browser to navigate back and forth in an application using the router.
Check out the following example to see this in action.
The main configuration can be found in the main.ts.
import { RouterConfiguration } from '@aurelia/router';
import { Aurelia, StandardConfiguration } from '@aurelia/runtime-html';
import { MyApp as component } from './my-app';
(async function () {
const host = document.querySelector<HTMLElement>('app');
const au = new Aurelia();
au.register(
StandardConfiguration,
RouterConfiguration.customize({
historyStrategy: 'push', // default value can can be omitted
})
);
au.app({ host, component });
await au.start();
})().catch(console.error);To demonstrate the push behavior, there is a small piece of code in the my-app.ts that listens to router events to create informative text (the history property in the class) from the browser history object that is used in the view to display the information.
import { resolve } from '@aurelia/kernel';
import { IHistory } from '@aurelia/runtime-html';
import { IRouterEvents } from '@aurelia/router';
export class MyApp {
private history: string;
public constructor() {
let i = 0;
const history = resolve(IHistory);
resolve(IRouterEvents).subscribe('au:router:navigation-end', () => {
this.history = `#${++i} - len: ${history.length} - state: ${JSON.stringify(history.state)}`;
});
}
}As you click the Home and About links in the example, you can see that the new states are being pushed to the history, and thereby increasing the length of the history.
replace
replaceThis can be used to replace the current state in the history. Check out the following example to see this in action. Note that the following example is identical with the previous example, with the difference of using the replace-value as the history strategy.
As you interact with this example, you can see that new states are replacing old states, and therefore, unlike the previous example, you don't observe any change in the length of the history.
none
noneUse this if you don't want the router to interact with the history at all. Check out the following example to see this in action. Note that the following example is identical with the previous example, with the difference of using the none-value as the history strategy.
As you interact with this example, you can see that there is absolutely no change in the history information, indicating non-interaction with the history object.
Override configured history strategy
You can use the navigation options to override the configured history strategy for individual routing instructions.
Configure active class
Using the activeClass option you can add a class name to the router configuration. This class name is used by the load custom attribute when the associated instruction is active. The default value for this option is null, which also means that the load custom attribute won't add any class proactively. Note that the router does not define any CSS class out-of-the-box. If you want to use this feature, make sure that you defines the class as well in your stylesheet.
// main.ts
RouterConfiguration.customize({
activeClass: 'active-route'
})/* styles.css */
.active-route {
font-weight: bold;
color: #007acc;
text-decoration: underline;
}<!-- These links will get the 'active-route' class when their routes are active -->
<a load="home">Home</a>
<a load="about">About</a>Disable navigation model generation
If you're not using the navigation model feature for building menus, you can disable it to improve performance:
RouterConfiguration.customize({
useNavigationModel: false
})This prevents the router from generating navigation model data, which can be useful in applications with many routes where you don't need the navigation model functionality.
Error recovery configuration
The restorePreviousRouteTreeOnError option controls what happens when navigation fails:
// Default behavior - restore previous route on error (recommended)
RouterConfiguration.customize({
restorePreviousRouteTreeOnError: true
})
// Strict mode - leave application in error state
RouterConfiguration.customize({
restorePreviousRouteTreeOnError: false
})With the default true setting, if navigation fails (due to guards returning false, component loading errors, etc.), the router will restore the previous working route. Setting this to false provides stricter error handling but requires your application to handle error states properly.
Observing navigation state while configuring the router
Beyond setting up routes, hash/push mode, or titles, you can optionally observe the active route and track query parameters. One way is to inject ICurrentRoute in any of your components. Another is to watch router events:
import { RouterConfiguration, IRouterEvents, NavigationEndEvent, ICurrentRoute } from '@aurelia/router';
import { DI } from '@aurelia/kernel';
const container = DI.createContainer();
container.register(
RouterConfiguration.customize({ useHref: false }) // for example
);
const routerEvents = container.get(IRouterEvents);
const currentRoute = container.get(ICurrentRoute);
routerEvents.subscribe('au:router:navigation-end', (evt: NavigationEndEvent) => {
console.log('Navigation ended on:', evt.finalInstructions.toUrl());
console.log('Active route object:', currentRoute.path);
});This can help debug or log your router's runtime state. See the ICurrentRoute docs for an example usage.
Treat query parameters as path parameters
When the treatQueryAsParameters property in the router configuration is set to true, the router will treat query parameters as path parameters. The default value is false.
This is a temporary option to help developers transitioning from router-direct to router. It will be removed in the future version.
Advanced Configuration Scenarios
Combining Multiple Options
Most real-world applications will need to combine multiple configuration options:
// Production-ready configuration
RouterConfiguration.customize({
useUrlFragmentHash: false, // Use clean URLs
historyStrategy: 'push', // Standard browser navigation
activeClass: 'active', // Highlight active nav items
useNavigationModel: true, // Enable navigation model for menus
restorePreviousRouteTreeOnError: true, // Graceful error recovery
buildTitle: (transition) => {
// Custom title building with SEO considerations
const routeTitle = transition.routeTree.root.children
.map(child => child.title)
.filter(title => title)
.join(' - ');
return routeTitle ? `${routeTitle} | My App` : 'My App';
}
})Environment-Specific Configuration
You might want different configurations for different environments:
// environment-based configuration
const isDevelopment = process.env.NODE_ENV === 'development';
const isProduction = process.env.NODE_ENV === 'production';
RouterConfiguration.customize({
useUrlFragmentHash: isDevelopment, // Hash routing in dev for simplicity
historyStrategy: isDevelopment ? 'replace' : 'push', // Keep history noise low in dev, full history in prod
restorePreviousRouteTreeOnError: !isDevelopment, // Let errors surface in dev, recover in prod
buildTitle: isProduction
? (tr) => buildSEOTitle(tr) // SEO-optimised titles in production
: (tr) => `[DEV] ${tr.routeTree.root.title ?? 'Unknown route'}`,
});Micro-frontend Configuration
When building micro-frontends, you might need specific base path configurations:
// Determine base path from current location
const currentPath = window.location.pathname;
const microFrontendName = currentPath.split('/')[1]; // e.g., 'admin', 'customer', 'reports'
RouterConfiguration.customize({
basePath: `/${microFrontendName}`,
useUrlFragmentHash: false,
historyStrategy: 'push',
buildTitle: (transition) => {
const appName = microFrontendName.charAt(0).toUpperCase() + microFrontendName.slice(1);
const routeTitle = transition.routeTree.root.children[0]?.title;
return routeTitle ? `${routeTitle} - ${appName}` : appName;
}
})Single-Page Application Embedded in Existing Site
When your Aurelia app is embedded within a larger traditional website:
RouterConfiguration.customize({
basePath: '/spa', // App lives under /spa path
useUrlFragmentHash: true, // Hash routing to avoid conflicts
historyStrategy: 'replace', // Don't interfere with main site navigation
useHref: false, // Only use load attribute to avoid conflicts
activeClass: 'spa-active', // Namespaced CSS class
})Common Configuration Patterns
Mobile-Optimized Configuration
RouterConfiguration.customize({
historyStrategy: 'replace', // Reduce memory usage on mobile
useNavigationModel: false, // Disable if using custom mobile navigation
restorePreviousRouteTreeOnError: true, // Important for unreliable mobile networks
})Debug-Friendly Development Configuration
RouterConfiguration.customize({
useUrlFragmentHash: true, // Easier to debug without server setup
restorePreviousRouteTreeOnError: false, // See errors clearly in development
buildTitle: (transition) => {
// Detailed debugging information in title
const route = transition.routeTree.root.children[0];
return `[${route?.component?.name || 'Unknown'}] ${route?.title || 'No Title'}`;
}
})Troubleshooting Configuration Issues
Common Problems and Solutions
Problem: Routes not working with useUrlFragmentHash: false
// Solution: Ensure base tag is set correctly
// In your index.html:
<base href="/">
// And configure your server for SPA routing
RouterConfiguration.customize({
useUrlFragmentHash: false
})Problem: External links being processed by router
// Solution 1: Disable href processing
RouterConfiguration.customize({
useHref: false // Only use load attribute for routing
})
// Solution 2: Mark external links explicitly
// <a href="mailto:[email protected]" external>Contact</a>Problem: Navigation not updating browser history
// Check your history strategy
RouterConfiguration.customize({
historyStrategy: 'push' // Ensure this is not 'none'
})Last updated
Was this helpful?