Just launched: 360° security audit to protect your legacy code from AI exploits.

Discover
Blog›Gen AI in Modernization

Ember.js to React Migration: A Quick Guide for Every Ember Version

In This Article

TL;DR

  • Identify the Ember era before planning the move. Classic components, observers, and mixins need redesign in React. Glimmer components and tracked properties map more directly.
  • Pick a bridge that supports the Ember version in production. The older React-in-Ember addons stop at React 17. A current toolkit targets Ember 4.12 and above, and a small createRoot wrapper covers apps outside that range.
  • Give each URL segment one router. Ember keeps the routes it owns, React Router takes over one segment at a time, and cross-boundary links go through the owning router.
  • Plan Ember Data as its own workstream. The store’s identity map, relationships, and custom adapters hold behavior that a React data library does not reproduce by default.
  • Use the acceptance tests the app already has as the parity record. A screen is done when those same tests pass on its React version.

Migrating from Ember.js to React Incrementally

An Ember.js app can move to React incrementally. React components run inside Ember through a bridge addon or a small wrapper built on createRoot. Teams then move screens and routes one at a time, replace Ember Data behind the same API, and remove Ember once no route depends on it.

How much of the work is a rewrite depends on which Ember the app runs. For many teams, the Ember move sits alongside other front-end upgrades within a broader frontend modernization plan.

When to Migrate from Ember.js to React

A working Ember app usually moves for one of three reasons.

  • Hiring. In the 2025 Stack Overflow Developer Survey, 46.9% of professional developers use React, and Ember.js is absent from the technology results [1].
  • Support windows. An Ember LTS release receives bug fixes for 36 weeks and security updates for 54 weeks [2]. The active LTS releases are 6.12 and 6.8, so apps on Ember 5 or earlier sit outside both windows.
  • Feature velocity. Each Ember screen shipped today becomes one more screen to convert later.

An app on a current Ember LTS, with an experienced team and few planned features, can stay on Ember for now. New screens can be built in React inside the Ember app from the first sprint, so feature work continues during the move.

Identifying Whether an Ember App Is Classic or Octane

Octane shipped with Ember 3.15 in December 2019, and its component model was opt-in and interoperable with existing code [3]. A single app can hold both eras, so the era check reads the code patterns in each route.

Classic Ember shows up in these patterns [4].

  • Components built with Component.extend from @ember/component, with tagName and wrapper elements
  • Controllers holding most page state, plus this.get and this.set calls
  • Computed properties, observers, and mixins
  • Lifecycle hooks such as didInsertElement, and two-way bound arguments
  • Older Ember CLI builds with no ember-auto-import or Embroider
  • this.$() jQuery calls inside components, deprecated in Ember 3.4 and removed in 4.0

Octane Ember shows up in these patterns.

  • Native class components extending @glimmer/component
  • @tracked properties, @action methods, and {{on}} in templates
  • Element modifiers for DOM behavior
  • Embroider, Vite, or ember-auto-import v2 in the build

Classic patterns have no direct React equivalent. Two-way bindings, observers, and mixins become explicit props, effects, and hooks, so more of the work is redesign. Octane components map more directly, since tracked state and one-way arguments already match React’s model.

Upgrading classic code to Octane first is an option. It produces a cleaner mapping and means converting each component twice, so it fits apps planning a long coexistence period.

Across hundreds of routes, the era split is a code-analysis job. Gen AI tooling that reads the whole codebase counts classic components, observers, and mixins per route, which sizes the redesign before any screen moves.

Know How Much of Your Ember App Is Classic Before Scoping the React Work

Dependency Map
Risk Heatmap
Modernization Plan (3-5 Days)
MedtronicClairULAB Systems+more

Choosing an Ember and React Bridge by Ember Version

A bridge renders React components inside Ember templates, and each option supports a specific range of Ember and React versions.

BridgeEmber fitReact supportStatus
ember-cli-reactApps on ember-auto-import v1React 15 and 16Last release in 2018, described in its README as experimental
ember-cli-react-fork (first published as ember-simple-react)Apps on ember-auto-import v1React 15 to 17Forked for inactivity, last release in 2021
@qonto/react-migration-toolkitEmber 4.12 or above with Embroider or ember-auto-import v2 [5]Ships React 18Last release in November 2025, declared peer range ends at Ember 5
Custom wrapper on createRootAny app that can import npm packagesCurrent React, including React 19Maintained by the team

The two inactive addons stop at React 17. Classic apps on these addons stay pinned to an old React, and adding ember-auto-import plus a custom wrapper removes that pin.

The toolkit passes Ember services and intl into React through providers. Version 4.0 removed its routing hooks, so navigation between the two frameworks needs its own callback. Apps on Ember 6 or 7 sit outside its declared peer range, so it needs a trial on a branch before adoption.

A custom wrapper needs one modifier. React supports several roots on a page built in another framework, and root.unmount has to run when another library removes the node [6].

// app/modifiers/react-root.js
import { modifier } from 'ember-modifier';
import { createElement } from 'react';
import { createRoot } from 'react-dom/client';

export default modifier((element, [Component], props) => {
  const root = createRoot(element);
  root.render(createElement(Component, props));
  return () => root.unmount();
});

A template mounts it with <div {{react-root this.CartSummary cart=@cart}}></div>. this.CartSummary is a property on the backing component class that holds the imported React component, since an .hbs template cannot import JavaScript.

The modifier recreates the root whenever an argument changes, which resets any React state inside it. That suits leaf components. Stateful components keep one root and call root.render again with new props, which a class-based modifier supports.

The build has to compile JSX, which ties the bridge to wider frontend toolchain modernization. React 19 and React DOM add about 67 kB gzipped to a minified production bundle.

Ember Concepts and Addons Mapped to React

EmberReact equivalentDuring migration
Classic componentFunction componentWrapper elements, two-way bindings, and observers are redesigned as props and effects
Glimmer componentFunction component@tracked state becomes useState, and this.args becomes props
Helpers and modifiersPlain functions and effectsModifiers become useEffect with cleanup or a ref callback
Route model() hookRoute loader or query hookLoading and error templates become loading and error states
Ember Data store and modelsA server-state library such as TanStack Query, plus fetch functionsCovered in the Ember Data section
ServicesContext or a store such as ZustandInjected services become providers or hooks
Computed and tracked propertiesValues derived during render, with useMemo where cost mattersDependent keys disappear
Routing addonsReact RouterTakes over one URL segment at a time
Form and validation addonsA form library such as React Hook FormValidation rules move with each form
Modal and UI kit addonsA React component library or headless UI primitivesRebuilt screen by screen
Testing addons (ember-qunit, Mirage)Vitest or Jest, Testing Library, Playwright, MSWAcceptance tests become browser tests

These are target-state equivalents. Until a screen moves, its React components get Ember data through props or bridge providers, and each replacement ships with the screen that needs it.

Moving Ember Routes to React Router Without Breaking URLs

Routing is the first of the two hard parts. Ember’s router and React Router run side by side under the strangler fig approach when each URL segment has one owner at a time.

React screens start as islands inside Ember routes, so Ember’s router keeps every URL early on. When a whole area moves, an Ember wildcard route such as /billing/*path hosts a React app whose router uses /billing as its basename. React Router then owns everything under that segment.

Links that cross the boundary go through the owning router. React code calls Ember’s RouterService.transitionTo through a callback passed from the host route, and Ember templates link into React segments by URL.

Ember apps configured with hash location carry bookmarked #/ URLs. Those links need a client-side redirect to the new paths, because browsers keep the fragment on the client.

The Ember application shell is removed once the last segment it owns moves to React Router.

One URL space with two routers during an Ember to React migration

Replacing Ember Data in an Ember to React Migration

Ember Data is the second hard part, because much of an Ember app’s data behavior lives inside it.

Identity and caching. The store identifies each record by model type and ID and returns the same record object on every lookup [7]. It also refreshes cached records with a background request [7]. Every component reading a record sees an edit at once.

A query-key cache such as TanStack Query stores each response separately. Two queries holding the same customer return two objects, so an edit has to update or invalidate both. Screens that depend on shared identity need a normalized store or explicit invalidation rules.

Relationships. @belongsTo and @hasMany load related records on demand and keep both sides in sync. In React, a relationship becomes an explicit query per resource, or an API response that includes the related data.

Adapters and serializers. Custom adapters and serializers hold the API’s quirks, including URL rules, key casing, embedded records, and error formats. Each one becomes a tested fetch or transform function before the first React screen depends on it.

Running both layers. Ember Data and the React data layer call the same API, with the contract unchanged. Ownership moves one resource at a time, and a resource leaves Ember Data once no Ember route reads it. A save in one layer leaves the other layer’s cache stale, so shared resources refetch after each write.

Ember Data and the React query cache share one API during migration

See How a Frontend Migration Is Sequenced and Delivered

See how the platform sequences a frontend migration and turns each step into a diff-based pull request reviewed by an engineer.

Validating Parity with Ember Acceptance Tests in an AI-Assisted Migration

Ember CLI creates a test file with each route, component, and model it generates, and ember generate acceptance-test adds tests for full user journeys. Many Ember apps therefore already carry a test suite, and its acceptance tests are the behavior record for the migration.

As long as React renders as islands inside Ember routes, the existing acceptance tests keep running against the same DOM. Assertions on React output use waitFor from @ember/test-helpers, because React renders outside Ember’s run loop and test waiters.

Before a segment moves to React Router, its journeys get ported to a browser test runner such as Playwright and proven green on Ember. After the move, that unchanged suite runs on React, and passing it is the exit criterion for every screen in the segment.

One suite can serve both versions when the React screens keep the Ember element ids, data-test attributes, and API mocks. AI-assisted conversion changes more code each week than a manual rewrite, so test generation has to keep pace, and each generated test goes through review like application code.

That review is one of the Gen AI safety nets a migration at this volume depends on. Parity works the same way in data migration validation.

How Legacyleap’s AI Agents Migrate Ember.js Applications to React

Legacyleap is a Gen AI-powered legacy application modernization platform built on multi-agent orchestration. On an Ember codebase, five specialized agents run the sequence this guide lays out, from the era check to parity, across the Assess, Comprehend, Modernize, Validate, and Deploy lifecycle.

  • Assessment Agent. Produces the era inventory, counting classic components, observers, mixins, and this.$() calls per route, alongside the dependency map, risk hotspots, and effort estimate.
  • Documentation Agent. Reconstructs each Ember Data model, relationship, adapter, and serializer, so the API quirks they hold are written down before any React fetch function replaces them.
  • Recommendation Agent. Chooses the bridge for the Ember version in production, then orders the URL segments, Engines, and data resources that move to React.
  • Modernization Agent. Converts components and routes into React as diff-based pull requests, with TODO markers where classic patterns need redesign. About 70% of the modernization work is automated.
  • QA Agent. Builds unit, integration, regression, and end-to-end tests next to the existing acceptance suite, and validates parity against the Ember baseline before each segment moves.

Each change lands as a pull request that an engineer reviews and merges. The agents cannot merge, deploy, or execute code. The platform runs inside the client’s environment, and no source code leaves it.

Legacyleap programs finish 50-70% faster than manual modernization, and every completed project validates 100% functional parity before deployment. Legacyleap has completed more than 150 production-grade assessments.

Teams without spare capacity hand the migration to Legacyleap’s team through Turnkey Modernization Services. Teams with their own front-end engineers or an SI run the platform under Platform Licensing.

For a front-end lead weighing modernization partners for a large Ember app, the $0 Modernization Assessment returns the era inventory, migration plan, and estimate with no obligation to proceed.

Next Steps for an Ember.js to React Migration

An Ember.js to React migration starts with the era check, which decides the bridge and how much of the classic code needs redesign. Routes and Ember Data then move one segment and one resource at a time. No Ember code is deleted until the React version of its screen passes the same tests.

The same coexistence method carries over to a Backbone.js to React migration.

The $0 Modernization Assessment is the starting point. It covers one representative Ember codebase, sizes its classic code and Ember Data layer, and returns a migration plan with an estimate in 3 to 5 days. It costs nothing and runs entirely inside your environment. Teams that want to see the platform first can Book a Demo.

Start the Migration With a Map of Your Ember Code

A $0 Modernization Assessment maps one representative Ember codebase, its components, routes, Ember Data layer, and risk areas, and returns a migration plan and estimate in 3 to 5 days, inside your own environment.

FAQs

Q1. Can a React form save changes through Ember Data?

Yes. It can call set and save on a record passed in as a prop. Ember screens update through tracked attributes, and the React component re-reads the record once save resolves.

Q2. Does the React bundle load on every Ember route?

Not when it loads through a dynamic import(), which Embroider and ember-auto-import v2 support. The static import in the modifier above puts React in the main bundle.

Q3. Can an Ember Engine migrate to React as one unit?

Yes. A routable engine already mounts at its own path, so it becomes one URL segment that React Router takes over once its components and data are converted.

Q4. Do Ember addons need replacing before the first React screen ships?

No. Each addon keeps serving the Ember routes that use it and is removed once the last of those routes moves to React.

Q5. Can React components use Ember services during the migration?

Yes. The bridge passes a service into React as a prop or through a context provider. Tracked changes on the service reach React when the bridge re-renders or when the component subscribes to a callback.

Q6. Is an Ember to React migration a full rewrite?

Components and templates are rewritten in React. Plain JavaScript modules, utility functions, and the API contract carry over, so the rewrite share depends on how much logic lives inside components.

References

[1] Stack Overflow Developer Survey 2025, Technology..

[2] Ember.js, “LTS Releases.”.

[3] Ember Blog, “Octane is Here.”.

[4] Ember Learn, “Ember Octane vs Classic Cheat Sheet.”.

[5] Qonto, “react-migration-toolkit” (GitHub)..

[6] React documentation, “createRoot.”.

[7] Ember Guides, “EmberData.”.

Book a $0 Assessment

We will scan a portion of your legacy codebase and share documentation, architecture maps, dependency graphs, in 3-5 days.

Book a Time →
Share the Blog

Latest Blogs

Migrate Oracle Forms to Java, .NET or APEX: How to Choose

Migrate Oracle Forms to Java, .NET or APEX: How to Choose and What It Takes

Backbone.js to React Migration: A Quick Guide

Backbone.js to React Migration: A Quick Guide for Large Codebases

VB.NET to React: Convert a Desktop App to a Web App

VB.NET Desktop Application to React Web App: A Conversion Guide

React vs Angular: Which Is Better for Enterprise Apps

React vs Angular: Which Is Better for Existing Enterprise Applications

Enterprise Application Modernization Planned Around Data

Enterprise Application Modernization: How to Assess, Sequence, and Validate a Portfolio With Gen AI

Legacy Code Modernization That Moves Code and Data Together

Legacy Code Modernization: A Practical Guide to Moving Code and Data Together

Technical Demo

Book a Technical Demo

Explore how Legacyleap’s Gen AI agents analyze, refactor, and modernize your legacy applications, at unparalleled velocity.

Watch how Legacyleap’s Gen AI agents modernize legacy apps ~50-70% faster

Want an Application Modernization Cost Estimate?

Get a detailed and personalized cost estimate based on your unique application portfolio and business goals.