Skip to content

Ember Project Structure — Understanding the Default Layout

DodaTech Updated 2026-06-28 4 min read

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

Ember's project structure follows strict conventions. Every file has a specific location based on its purpose. The resolver uses these conventions to automatically wire routes to templates, components to templates, and models to routes without manual imports.

What You'll Learn

You will understand every directory and file in an Ember project, how the resolver maps names to files, and how to organize code following Ember best practices.

Why It Matters

Ember conventions mean every project looks the same. When you join a new Ember team, you already know where files live. This consistency is one of Ember's strongest advantages for team productivity.

Real-World Use

A team of 15 developers maintains an Ember application with 200+ routes. New developers become productive in days because the project structure is predictable. Every route is in app/routes/, every component in app/components/.

flowchart TD
    A[app/] --> B[components/]
    A --> C[controllers/]
    A --> D[helpers/]
    A --> E[models/]
    A --> F[routes/]
    A --> G[services/]
    A --> H[styles/]
    A --> I[templates/]
    A --> J[router.js]
    A --> K[app.js]

The app/ Directory

This is where all application code lives. Everything inside app/ is automatically resolved by name.

app/
  components/         # Reusable UI components
  controllers/        # Route controllers (optional with Ember Octane)
  helpers/            # Template helper functions
  models/             # Ember Data models
  routes/             # Route handlers
  services/           # Singleton services
  styles/             # Component CSS
  templates/          # Handlebars templates
  app.js              # Application entry point
  index.html          # HTML shell
  router.js           # Route map

The app.js Entry Point

// app/app.js
import Application from '@ember/application';
import Resolver from 'ember-resolver';
import loadInitializers from 'ember-load-initializers';
import config from './config/environment';

export default class App extends Application {
  modulePrefix = config.modulePrefix;
  podModulePrefix = config.podModulePrefix;
  Resolver = Resolver;
}

loadInitializers(App, config.modulePrefix);

The router.js File

This file defines the route map. Routes are nested using closures.

// app/router.js
import EmberRouter from '@ember/routing/router';
import config from './config/environment';

export default class Router extends EmberRouter {
  location = config.locationType;
  rootURL = config.rootURL;
}

Router.map(function() {
  this.route('about');
  this.route('contact');

  this.route('posts', function() {
    this.route('post', { path: ':post_id' });
  });

  this.route('admin', function() {
    this.route('users');
    this.route('settings');
  });
});

Routes Directory

Each route file handles data loading for its template.

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

export default class AboutRoute extends Route {
  model() {
    return {
      title: 'About Us',
      team: ['Alice', 'Bob', 'Charlie']
    };
  }
}

Templates Directory

Templates use Handlebars syntax. Each route has a corresponding template.

{{! app/templates/about.hbs }}
<div class="about-page">
  <h1>{{this.model.title}}</h1>

  <h2>Our Team</h2>
  <ul>
    {{#each this.model.team as |member|}}
      <li>{{member}}</li>
    {{/each}}
  </ul>

  <LinkTo @route="contact">Contact Us</LinkTo>
</div>

Components Directory

Components have a JavaScript file and a template file.

// app/components/nav-bar.js
import Component from '@glimmer/component';

export default class NavBarComponent extends Component {
  get isLoggedIn() {
    return !!this.args.session?.user;
  }
}
{{! app/components/nav-bar.hbs }}
<nav class="nav-bar">
  <LinkTo @route="index">Home</LinkTo>
  <LinkTo @route="about">About</LinkTo>

  {{#if this.isLoggedIn}}
    <LinkTo @route="profile">Profile</LinkTo>
    <button type="button" {{on "click" this.args.logout}}>Logout</button>
  {{else}}
    <LinkTo @route="login">Login</LinkTo>
  {{/if}}
</nav>

Models Directory

Ember Data models define attributes and relationships.

// app/models/post.js
import Model, { attr, hasMany, belongsTo } from '@ember-data/model';

export default class PostModel extends Model {
  @attr('string') title;
  @attr('string') body;
  @attr('date') createdAt;
  @belongsTo('user') author;
  @hasMany('comment') comments;
}

Services Directory

Services are singletons shared across the application.

// app/services/session.js
import Service from '@ember/service';

export default class SessionService extends Service {
  user = null;

  login(email, password) {
    // Authentication logic
  }

  logout() {
    this.user = null;
  }

  get isAuthenticated() {
    return !!this.user;
  }
}

Config Directory

// config/environment.js
module.exports = function(environment) {
  let ENV = {
    modulePrefix: 'my-ember-app',
    environment: environment,
    rootURL: '/',
    locationType: 'history',
    EmberENV: {
      EXTEND_PROTOTYPES: false,
      FEATURES: {}
    },
    APP: {}
  };

  if (environment === 'production') {
    // Production-specific config
  }

  return ENV;
};

Common Mistakes

  1. Putting files in wrong directories. routes/about.js is a route handler. components/about.js is a component. The resolver treats them differently.
  2. Forgetting to update router.js after creating routes manually. Always use ember generate route to keep the router in sync.
  3. Creating controllers when not needed. Ember Octane (3.15+) makes controllers optional. Use components with @tracked properties instead.
  4. Storing templates in the wrong location. Templates for routes go in app/templates/. Templates for components go in app/components/.
  5. Editing app/index.html unnecessarily. app/index.html is the HTML shell. Most content belongs in templates.

Practice Questions

  1. What is the purpose of router.js?
  2. Where do route handler files live?
  3. How does Ember resolve which template to render for a route?
  4. What is the difference between a Service and a Component?
  5. Challenge: Create an Ember project with routes for articles, articles/article (with dynamic segment), and profile. Add a service called user-preferences. Add a component called article-card. Verify the resolver loads all files correctly.

FAQ

Can I change Ember's default directory structure?

It is possible but strongly discouraged. All Ember tooling assumes the default structure.

What is the resolver?

The resolver maps names to files. When you reference a route 'about', it finds app/routes/about.js.

Where do I put CSS files?

Global CSS goes in app/styles/app.css. Component CSS can be co-located using CSS modules or Tailwind.

What is `ember-cli-build.js`?

It configures the Broccoli build pipeline. Use it to configure asset compilation.

Where do static assets go?

Files in public/ are copied directly to the build output.

Mini Project

Create a new Ember project. Generate four routes: home, products, products/product, and cart. Generate a component called product-card and a service called shopping-cart. Write the route handler for products that returns a list of products. Wire the navigation in application.hbs.

What's Next

Now that you understand the project structure, learn about Ember Routes for handling URL-based navigation. Then explore Ember Route Hooks for data loading.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro