From Angular to React: A Migration Journey
Lessons from gradually migrating a large Angular codebase to React — strangler-fig routing, shared state, and keeping the team shipping throughout.
We didn't set out to migrate. The plan was to add one new feature in React because we'd hired two people who didn't want to learn AngularJS, and that one feature turned into two, and then a whole route, and before anyone had blessed the idea there was a React app living inside our Angular app and nobody could quite explain how. The honest version of our migration story is that it was never a decision — it was a drift we eventually made official.
Strangle the routes, not the codebase
The single best thing we did was put a thin router at the edge. It was an nginx config, nothing clever, and it checked the path against a list before handing the request to either the Angular bundle or the React one. Neither app knew the other existed. We could add a route to the list the moment it was ready, and roll one back just as fast. Two years later, the list was empty and we deleted the router.
Don't share state. Share auth.
There was a long meeting in the first month where we nearly agreed to write an adapter between the two stores. It would have cost us a quarter and none of the users would have noticed the difference. Instead we shipped a tiny /me endpoint both apps could hit. A session cookie, a small profile blob. Each app kept its own UI state. The seam was invisible to users, and it never bit us.
Design system first, features second
The move that paid off most wasn't technical. We built the button, input, and layout primitives in React before migrating any real feature. Published them as an internal package. Then we went back and consumed them from the Angular app too. The visual seam between old and new closed six months before the code seam did, which meant users stopped noticing the migration was even happening. That's the dream, right? The thing you worked on for a year, and nobody clocked it.
The goal of migration isn't to finish — it's to never stop shipping.
What I'd do differently
- Spend week one on a killswitch. We didn't, and the first rollback took four hours when it should have taken four minutes.
- Put analytics on both stacks before moving anything. 'It feels faster' is not an argument you can win in front of leadership.
- Keep a public list of routes still on the old stack. It's the only metric that genuinely tracks progress, and everyone on the team should be able to watch it shrink.
Two things surprised me. One, the migration took eighteen months longer than we estimated, which is either a lot or exactly the industry average depending on who you ask. Two, by the end, half the team didn't remember what the old app looked like. The new one had simply become what the product was.