Strangler routes: migrating off Ember without a rewrite

When a company decides to move off Ember, the first plan proposed is almost always a rewrite: stand up a fresh React app, rebuild screens for a year, cut over on a weekend. We've been called into enough of these afterwards to describe the standard failure precisely — the old app keeps changing while the new one chases it, the business tires of paying two teams around month nine, and the project dies with the new app at 70% and nothing shipped. The alternative that actually completes is the strangler pattern, and in an Ember app the natural seam for it is the route.

Why the route is the right boundary

Ember's router owns the URL, and URLs are the one contract everyone honors — bookmarks, links from emails, your own navigation. A route is also a real ownership boundary: its model hooks, its controller state, its templates. That makes "this URL prefix is now served by the new stack" a migration increment you can ship, measure, and roll back, while both stacks keep working against the same backend.

Settle the foundations before moving the first route

The migrations that go smoothly spend their first weeks on shared infrastructure, not on screens:

  • Session and auth. Both stacks must agree on how a user is authenticated. A cookie-based session shared at the domain level is the simple answer; if the Ember app carries tokens in localStorage under its own conventions, standardize that before anything moves.
  • Routing shell. Decide who owns the address bar. The robust pattern is a thin boundary — a reverse proxy or the server's router — mapping URL prefixes to apps, with full-page navigation between stacks. In-app mounting of one framework inside the other is possible, but every hour spent on that plumbing is an hour not spent retiring Ember code, and the full-page hop between sections is cheaper than teams assume once assets are cached and split.
  • Design tokens and layout. Extract colors, spacing, typography, and the app shell (header, nav, footer) into something both stacks render identically, so users don't experience the migration as a redesign. If a redesign is genuinely wanted, do it in the Ember app first or the new stack first — never as a per-route surprise.

Choose routes by churn, not by size

Sequence with two questions: how much does product change this screen, and how tangled is it with shared state? Low-churn leaf routes go first — settings pages, admin screens, read-mostly reports. They prove the pipeline end to end while the blast radius is small. High-churn screens under active development go last, or wait until their churn settles; migrating a screen the product team is redesigning weekly means implementing every change twice. The worst candidates are routes deeply coupled to app-wide client state; each of those needs its state dependency broken (usually by pushing the source of truth to the server) before it can move.

Parallel-run like you mean it

"Shipped" is not the bar; "proven" is. Each moved route goes out behind a flag or a percentage rollout, with error rates, performance, and the route's own business metrics compared against the Ember version, and a rollback path that stays warm until confidence is earned. This is also your protection against the subtle killers — the Ember route that was quietly normalizing data, the query param nobody documented — because they show up as metric deltas instead of support tickets.

Retire code relentlessly

The point of the pattern is that Ember code goes away as increments land. Delete the migrated routes, their components, and — the part teams forget — the addons that nothing references anymore. A shrinking Ember app is the visible progress bar that keeps organizational patience funded, and a smaller app is also cheaper to keep healthy for the remainder of the migration, which is measured in quarters, not sprints.

Know when to stop

Run the numbers per section, and let them be wrong in either direction. Sometimes the last corner of the app — stable, complex, rarely touched — never justifies the cost of moving. Leaving a small, well-tested Ember island behind a stable URL prefix is a legitimate end state, not a failure. The goal was never "zero Ember"; it was an app your team can staff, ship, and afford. Strangler migrations end when that's true.