@glimmer/component vs @ember/component: Which Should You Use in Ember?
I was starting a new Ember project and typed import Component from in my editor. Two autocomplete options appeared: @glimmer/component and @ember/component. Which one should I use?
This isn’t just an IDE curiosity. The import I choose affects how I write components, manage state, handle lifecycle events, and whether my code follows modern Ember best practices.
The Confusion
When Ember Octane was introduced in Ember 3.13, it brought a completely new component model. But Ember maintained backward compatibility with the classic component API. This left many developers wondering: which import is the “right” one?
I tried using @ember/component in a new Octane project and quickly ran into issues:
import Component from '@ember/component';
export default class MyComponent extends Component { // Classic lifecycle hooks didInsertElement() { console.log('Component inserted'); }}The component worked, but I was missing out on all the Octane benefits: tracked properties, native class syntax, and better performance. I was essentially writing legacy code in a modern project.
The Solution
For all new Ember development, use @glimmer/component. Here’s why:
Use @glimmer/component When:
- Starting a new Ember project (Octane edition)
- Creating new components in an existing Octane codebase
- Building template-only components
- You want native JavaScript class syntax
- You need tracked properties for reactivity
Use @ember/component When:
- Maintaining legacy Ember code that hasn’t migrated to Octane
- You need classic lifecycle hooks like
didInsertElementorwillDestroyElement - Gradually migrating a codebase from classic to Octane
Comparison Table
| Feature | @glimmer/component | @ember/component ||----------------------|-------------------------|----------------------------|| Class Syntax | Native JS classes | EmberObject.extend() || State Management | Tracked properties | Computed properties || Lifecycle | Modifiers (did-insert) | Hooks (didInsertElement) || Performance | More efficient | Classic overhead || Future | Recommended path | Maintained for compat || Template-Only | Supported | Not applicable |Modern Glimmer Component Example
Here’s how I write components now:
import Component from '@glimmer/component';import { tracked } from '@glimmer/tracking';import { action } from '@ember/object';
export default class UserProfile extends Component { @tracked isEditing = false;
@action toggleEdit() { this.isEditing = !this.isEditing; }}<div class="user-profile"> {{#if this.isEditing}} <input value={{@user.name}} /> <button {{on "click" this.toggleEdit}}>Save</button> {{else}} <p>{{@user.name}}</p> <button {{on "click" this.toggleEdit}}>Edit</button> {{/if}}</div>Key differences from classic components:
- Native class syntax - No
EmberObject.extend() - Tracked properties -
@trackedinstead of computed properties - Arguments passed via
@args- Nothis.get('property') - No built-in lifecycle hooks - Use modifiers instead
Classic Ember Component Example (Legacy)
For comparison, here’s the classic approach I used to write:
import Component from '@ember/component';import { computed } from '@ember/object';
export default Component.extend({ isEditing: false,
userName: computed('user.name', function() { return this.get('user.name'); }),
actions: { toggleEdit() { this.toggleProperty('isEditing'); } }});The classic API works, but it requires more boilerplate and doesn’t benefit from Octane’s performance improvements.
Template-Only Components
One of my favorite Octane features is template-only components. When I don’t need any JavaScript logic, I just create a template file:
<div class="author-bio"> <img src={{@author.avatarUrl}} alt={{@author.name}} /> <h3>{{@author.name}}</h3> <p>{{@author.bio}}</p></div>No JavaScript file needed. This works because @glimmer/component supports template-only components natively.
The Lifecycle Difference
I initially struggled with lifecycle handling. In classic components, I used hooks:
import Component from '@ember/component';
export default Component.extend({ didInsertElement() { this._super(...arguments); // Setup code },
willDestroyElement() { this._super(...arguments); // Cleanup code }});In Glimmer components, these hooks don’t exist. Instead, I use modifiers:
<div {{did-insert this.setup}} {{will-destroy this.cleanup}}> Content here</div>import Component from '@glimmer/component';import { action } from '@ember/object';
export default class MyComponent extends Component { @action setup(element) { // Setup code }
@action cleanup(element) { // Cleanup code }}Ember 6.11 made this even better. The release notes mentioned that the internal default export from @glimmer/component was renamed from “GlimmerComponent” to “Component”. This improved IDE autocomplete - when I type “Component”, both import options appear, making the decision point clearer.
Common Mistakes
I made these mistakes when transitioning:
1. Using classic hooks in Glimmer components
import Component from '@glimmer/component';
export default class MyComponent extends Component { // This won't work - hooks don't exist in Glimmer components didInsertElement() { console.log('Never called'); }}2. Not updating imports during migration
import Component from '@ember/component';import { tracked } from '@glimmer/tracking'; // Wrong - mixing classic and Octane
export default class MyComponent extends Component { @tracked count = 0;}This technically works but creates confusion. Either fully migrate to Glimmer components or stick with classic patterns.
3. Mixing component types in the same file hierarchy
Keep Glimmer components and classic components separate. Mixing them in the same components/ directory structure makes maintenance harder.
Migration Strategy
When migrating a classic Ember codebase to Octane, I follow this approach:
- New components - Always use
@glimmer/component - Simple components - Migrate first (template-only or minimal logic)
- Complex components - Keep classic during transition, migrate later
- Use codemods - Ember provides official codemods for migration
The key is gradual migration. Both component types coexist in the same application without issues.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
In this post, I explored the differences between @glimmer/component and @ember/component. For new Ember projects, @glimmer/component is the clear choice - it provides native class syntax, tracked properties, better performance, and represents the future of Ember development. The classic @ember/component API remains available for maintaining legacy codebases, but new components should always use the Glimmer component API.
Comments