Skip to content

Aurelia Routing — Navigating Between Pages

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Aurelia Routing. We cover key concepts, practical examples, and best practices to help you master this topic.

Aurelia's router maps URLs to components. It supports nested routes, route parameters, query strings, route guards, and programmatic navigation. The router is configured in the application shell and renders matched components in a <router-view> element.

What You'll Learn

You will learn how to configure routes, navigate between pages, use route parameters, implement route hooks, and build complex navigation structures.

Why It Matters

Routing is the backbone of multi-page applications. Aurelia's router provides URL-driven navigation with browser history support, making applications bookmarkable and navigable with the back button.

Real-World Use

An admin panel uses the router for section navigation (dashboard, users, settings, reports). Each section has nested routes for list, detail, and edit views. Route hooks protect admin routes from unauthorized access.

flowchart LR
    A[Router Configuration] --> B[/dashboard]
    A --> C[/users]
    A --> D[/users/:id]
    A --> E[/settings]
    B --> F[Dashboard Component]
    C --> G[User List]
    D --> H[User Detail]
    E --> I[Settings Form]

Basic Route Configuration

// src/app.ts
import { RouterConfiguration, Router } from 'aurelia-router';
import { PLATFORM } from 'aurelia-pal';

export class App {
  router;

  configureRouter(config, router) {
    this.router = router;
    config.title = 'My App';
    config.options.pushState = true;
    config.options.root = '/';

    config.map([
      { route: '',            name: 'home',    moduleId: PLATFORM.moduleName('./home') },
      { route: 'about',       name: 'about',   moduleId: PLATFORM.moduleName('./about') },
      { route: 'contact',     name: 'contact', moduleId: PLATFORM.moduleName('./contact') },
      { route: 'products',    name: 'products', moduleId: PLATFORM.moduleName('./products') }
    ]);
  }
}
<!-- src/app.html -->
<template>
  <nav>
    <a route-href="route: home">Home</a>
    <a route-href="route: about">About</a>
    <a route-href="route: contact">Contact</a>
    <a route-href="route: products">Products</a>
  </nav>

  <router-view></router-view>
</template>

Route Parameters

config.map([
  // Dynamic segments
  { route: 'products/:id',          name: 'product-detail', moduleId: PLATFORM.moduleName('./product-detail') },
  { route: 'users/:username',       name: 'user-profile',   moduleId: PLATFORM.moduleName('./user-profile') },
  { route: 'categories/:slug/posts/:postId', name: 'post',  moduleId: PLATFORM.moduleName('./post') },

  // Optional parameters
  { route: 'search/:query?',        name: 'search',         moduleId: PLATFORM.moduleName('./search') },

  // Wildcard route (404)
  { route: 'not-found',             name: 'not-found',      moduleId: PLATFORM.moduleName('./not-found') }
]);
// src/product-detail.ts
import { inject } from 'aurelia-framework';
import { Router } from 'aurelia-router';

@inject(Router)
export class ProductDetail {
  constructor(router) {
    this.router = router;
  }

  activate(params, routeConfig) {
    this.productId = params.id;
    console.log('Loading product:', params.id);
    // Fetch product data using params.id
  }
}
// Programmatic navigation
export class NavComponent {
  constructor(router) {
    this.router = router;
  }

  goHome() {
    this.router.navigate('');
  }

  goToProduct(id) {
    this.router.navigateToRoute('product-detail', { id: id });
  }

  goToSearch(query) {
    this.router.navigateToRoute('search', { query: query });
  }

  goBack() {
    this.router.navigateBack();
  }

  refresh() {
    this.router.refresh();
  }

  redirectToLogin() {
    this.router.navigate('login', { replace: true, trigger: true });
  }
}
export class AdminRoute {
  canActivate(params, routeConfig, navigationInstruction) {
    // Check authentication
    if (!this.auth.isAuthenticated) {
      this.router.navigate('login');
      return false; // Cancel navigation
    }

    if (!this.auth.isAdmin) {
      this.router.navigate('forbidden');
      return false;
    }

    return true; // Allow navigation
  }

  activate(params) {
    // Route activated — load data
    return this.api.getAdminData();
  }

  canDeactivate() {
    // Confirm before leaving with unsaved changes
    if (this.hasUnsavedChanges) {
      return confirm('You have unsaved changes. Leave anyway?');
    }
    return true;
  }

  deactivate() {
    // Clean up resources
    this.dispose();
  }
}

Child Routers

// src/admin.ts — Parent component with child router
export class Admin {
  configureRouter(config, router) {
    config.map([
      { route: '',            name: 'dashboard',  moduleId: PLATFORM.moduleName('./admin/dashboard') },
      { route: 'users',       name: 'users',      moduleId: PLATFORM.moduleName('./admin/users') },
      { route: 'users/:id',   name: 'user-detail',moduleId: PLATFORM.moduleName('./admin/user-detail') },
      { route: 'settings',    name: 'settings',   moduleId: PLATFORM.moduleName('./admin/settings') }
    ]);
  }
}
<!-- src/admin.html — Parent layout with child router -->
<template>
  <div class="admin-layout">
    <aside>
      <a route-href="route: dashboard">Dashboard</a>
      <a route-href="route: users">Users</a>
      <a route-href="route: settings">Settings</a>
    </aside>
    <main>
      <router-view></router-view>
    </main>
  </div>
</template>

Pipeline Steps

Customize the navigation pipeline with steps.

import { Redirect } from 'aurelia-router';

export class AuthStep {
  run(navigationInstruction, next) {
    if (navigationInstruction.getAllInstructions().some(i => {
      return i.config.auth !== false;
    })) {
      // Check if user is authenticated
      if (!isAuthenticated) {
        return next.cancel(new Redirect('login'));
      }
    }
    return next();
  }
}

Register in config:

configureRouter(config, router) {
  config.addPipelineStep('authorize', AuthStep);
  config.map([...]);
}

Loading and Error States

<template>
  <div class="page">
    <!-- Loading indicator during route activation -->
    <div if.bind="router.isNavigating" class="loading-bar">
      Loading...
    </div>

    <!-- Error display -->
    <div if.bind="error" class="error-message">
      ${error.message}
    </div>

    <!-- Route content -->
    <router-view></router-view>
  </div>
</template>

Hash vs PushState

// Hash-based URLs (default): #/products/123
config.options.pushState = false;

// HTML5 History API: /products/123 (requires server config)
config.options.pushState = true;
config.options.root = '/';

Common Mistakes

  1. Not using PLATFORM.moduleName for module paths. Without it, routes break in production builds.
  2. Forgetting pushState: true requires server configuration. PushState URLs cause 404 on page refresh without server fallback.
  3. Not implementing canActivate for protected routes. Without route guards, protected pages are accessible to anyone.
  4. Over-nesting routes. Keep hierarchy to 3 levels max. Deep nesting complicates navigation and data loading.
  5. Not handling the 404 route. Always include a catch-all route for unknown URLs.

Practice Questions

  1. How do you configure routes in Aurelia?
  2. How do you access route parameters in a component?
  3. What is the difference between navigate and navigateToRoute?
  4. How do you protect routes with authentication?
  5. Challenge: Create an application with the following route structure: / (home), /products (list), /products/:id (detail), /products/:id/edit (edit, protected), /admin (admin panel with child router), /auth/login (login), /* (404). Implement route guards for admin and edit routes. Include loading and error states.

FAQ

What is the difference between `activate` and `canActivate`?

canActivate decides whether navigation is allowed. activate is called when navigation proceeds.

How do I pass data between routes?

Use a shared service or route parameters. Avoid relying solely on route state.

Can I have multiple routers on one page?

Yes. Use child routers for nested navigation sections.

How do I handle query parameters?

Access them in activate via params or use Router.currentInstruction.queryParams.

Does Aurelia support lazy loading?

Yes. Dynamic imports with PLATFORM.moduleName enable lazy loading automatically.

Mini Project

Build a documentation site with routes for sections and pages. Root router has top-level sections. Each section has a child router for pages within the section. Include: breadcrumb component, table of contents sidebar, search page with query param, 404 page, and loading indicators. Use canActivate to restrict admin pages.

What's Next

Now that you understand routing, learn Aurelia Router Lifecycle for navigation hooks. Then explore Aurelia Child Routers for nested navigation.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro