Skip to content

Ember Routes — URL-Based Navigation and Routing

DodaTech Updated 2026-06-28 4 min read

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

Ember Routes map URLs to templates and data. The router.js file defines the route map, and each route automatically loads its corresponding template. Routes handle data loading through the model hook and manage transitions through lifecycle hooks.

What You'll Learn

You will learn how to define routes, create dynamic segments, nest routes, control route ordering, and understand how Ember resolves which route matches a URL.

Why It Matters

Routing is the backbone of any Ember application. Every page, every navigation, every data load starts with the router. Mastering routes means mastering application flow.

Real-World Use

An e-commerce platform uses nested routes: /products/:category/:product_id for browsing, /cart for the shopping cart, and /checkout/:step for the multi-step checkout Process.

flowchart LR
    A[/products] --> B[Route: products.index]
    A --> C[/products/electronics] --> D[Route: products.category]
    A --> E[/products/123] --> F[Route: products.product]
    G[/cart] --> H[Route: cart]
    I[/checkout/shipping] --> J[Route: checkout.shipping]

Basic Route Definition

Routes are defined in app/router.js inside the Router.map() callback.

// app/router.js
Router.map(function() {
  this.route('about');           // /about
  this.route('contact');         // /contact
  this.route('faq');             // /faq
  this.route('pricing');         // /pricing
});

Each route automatically expects:

  • A route handler at app/routes/about.js
  • A template at app/templates/about.hbs

Dynamic Segments

Use { path: ':param' } to capture variable URL segments.

Router.map(function() {
  this.route('post', { path: '/post/:post_id' });
  this.route('user', { path: '/user/:username' });
  this.route('category', { path: '/category/:slug' });
});

The route handler receives the dynamic segment as an argument to model():

// app/routes/post.js
import Route from '@ember/routing/route';

export default class PostRoute extends Route {
  model(params) {
    console.log('Loading post:', params.post_id);
    return this.store.findRecord('post', params.post_id);
  }
}

Nested Routes

Routes can be nested to create hierarchical URLs and shared layouts.

Router.map(function() {
  this.route('settings', function() {
    this.route('profile');       // /settings/profile
    this.route('security');      // /settings/security
    this.route('notifications'); // /settings/notifications
  });

  this.route('dashboard', function() {
    this.route('analytics');     // /dashboard/analytics
    this.route('reports');       // /dashboard/reports
  });
});

Nested routes share the parent layout via {{outlet}}:

{{! app/templates/settings.hbs }}
<div class="settings-layout">
  <aside>
    <LinkTo @route="settings.profile">Profile</LinkTo>
    <LinkTo @route="settings.security">Security</LinkTo>
    <LinkTo @route="settings.notifications">Notifications</LinkTo>
  </aside>
  <main>
    {{outlet}}  {{! Child route renders here }}
  </main>
</div>

Index Routes

Every route can have an index sub-route that renders when the parent URL is matched without additional segments.

Router.map(function() {
  this.route('products', function() {
    // /products renders products/index
    this.route('product', { path: ':product_id' });  // /products/123
  });
});

Create app/routes/products/index.js and app/templates/products/index.hbs for the index route.

Route Ordering

Ember matches routes in the order they are defined. Define more specific routes before dynamic ones.

// GOOD: specific routes before dynamic
Router.map(function() {
  this.route('popular');          // /popular
  this.route('recent');           // /recent
  this.route('post', { path: ':post_id' });  // /123
});

// BAD: dynamic route catches everything
Router.map(function() {
  this.route('post', { path: ':post_id' });
  this.route('popular');  // NEVER reached — :post_id matches "popular"
});

Wildcard Routes

Use { path: '*path' } for catch-all routes like 404 pages.

Router.map(function() {
  this.route('not-found', { path: '/*path' });
});
// app/routes/not-found.js
import Route from '@ember/routing/route';

export default class NotFoundRoute extends Route {
  model(params) {
    console.warn('404 — Unknown route:', params.path);
    return params;
  }
}

Transition and URL Generation

// From a component or route
import { inject as service } from '@ember/service';
import Component from '@glimmer/component';

export default class NavComponent extends Component {
  @service router;

  goToPost(id) {
    // Transition to route
    this.router.transitionTo('post', id);

    // Generate URL without navigating
    let url = this.router.urlFor('post', id);
    console.log('URL would be:', url);
  }
}
{{! Template: LinkTo for navigation }}
<LinkTo @route="post" @model={{post.id}}>Read more</LinkTo>

Common Mistakes

  1. Defining routes in the wrong order. Dynamic segments catch all values. Define static routes before dynamic ones.
  2. Forgetting {{outlet}} in parent templates. Nested route templates never render without an {{outlet}} in the parent.
  3. Using this.route without a callback inside nested routes. Resources with children use function() callback. Resources without children omit it.
  4. Not creating index routes for nested resources. /products expects products/index.js. Without it, the template is blank.
  5. Using path incorrectly. { path: '/blog/:post_id' } defines the URL pattern. Do not include the leading / inside path — Ember adds it.

Practice Questions

  1. How do you define a dynamic segment in an Ember route?
  2. What is the purpose of {{outlet}} in nested routes?
  3. How do you create a catch-all 404 route?
  4. Why does route ordering matter?
  5. Challenge: Create a router with nested routes for a blog: /blog, /blog/:category, /blog/:category/:post_id, and /about. Add an index route for blog. Verify URL matching works correctly.

FAQ

How do I redirect from one route to another?

Use this.replaceWith('route-name') in a route's beforeModel hook.

Can I have optional dynamic segments?

No. Use query parameters for optional values.

What is the difference between `transitionTo` and `replaceWith`?

transitionTo adds a history entry. replaceWith replaces the current entry.

How do I access route params in a component?

Pass them as arguments: <Post @postId={{this.router.currentRoute.attributes.post_id}} />

Can I use `queryParams` in route definitions?

Yes: this.route('search', { queryParams: ['q', 'page'] });

Mini Project

Create an Ember app with the following routes: home (index), products/:category/:product_id, cart, checkout/:step (with steps: shipping, payment, confirm), and a catch-all 404. Add navigation in application.hbs. Verify all routes render correct templates.

What's Next

Now that you understand routes, explore Ember Route Hooks for data loading. Then learn Ember Nested Routes for complex layouts.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro