If you are maintaining an AngularJS application that has been running in production for years, you know the pain: the framework is no longer supported, new features are harder to add, and hiring developers who want to work with it is nearly impossible. The obvious answer is to migrate to React, but the obvious path—rewrite everything and flip a switch—is terrifying. One bad deploy could take down the entire site. This playbook lays out seven steps to move your legacy AngularJS app to React incrementally, without ever breaking the live experience for users.
1. Why Incremental Migration Beats a Rewrite
Teams that attempt a full rewrite of a large AngularJS application into React almost always fail to ship it within the original timeline. The business cannot pause feature development for six months, and the rewritten codebase often misses subtle edge cases that the old system handled. An incremental migration—sometimes called the strangler fig pattern—lets you run both frameworks side by side, replacing pieces one at a time. Each swap is small enough to test and roll back independently.
The core mechanism is simple: you serve your existing AngularJS app as before, but you carve out a route or a component and render it with React instead. A shared orchestration layer (often a micro-frontend shell) decides which framework renders which part of the page. Over time, the React surface grows and the AngularJS surface shrinks until the old framework has nothing left to do. This approach keeps the site live during the entire process, and you can pause the migration at any point if higher-priority features come up.
What usually breaks first
When you start mixing frameworks, the first casualty is global state. AngularJS uses its own digest cycle and two-way data binding, while React relies on a unidirectional flow. If you have a piece of state that both frameworks need to read or update (like a user session or a shopping cart), you will see flickering, stale data, or infinite loops. Planning a shared state mechanism before you write any React code saves weeks of debugging.
2. Prerequisites: What You Need Before Writing a Single React Component
Before you start any migration, you need a clear picture of your current AngularJS codebase. Run a static analysis tool (like ng-stats or a custom script) to count directives, services, controllers, and routes. Identify the most isolated pieces—widgets that do not depend heavily on global services or the digest cycle. Those are your first candidates for replacement.
Next, decide on your co-existence architecture. The most reliable pattern today is to use Webpack 5 with Module Federation. This allows you to build your AngularJS app and your React app as separate bundles that share a common shell. The shell is a plain JavaScript file that mounts the appropriate framework for each route. You can also use an iframe-based isolation, but that breaks URL sharing and makes integration testing harder.
Tooling checklist
- Webpack 5 with Module Federation plugin
- A shared build process that can compile both AngularJS (via a compatible version of Babel or TypeScript) and React (via Babel with @babel/preset-react)
- A consistent CSS strategy—CSS Modules or a utility framework like Tailwind—so styles do not leak between frameworks
- An integration testing framework (Cypress or Playwright) that can run tests across the whole hybrid app
State management bridge
You need a way for AngularJS and React to share data without circular dependencies. A common solution is to create a small event bus or use a shared Redux store that both frameworks can read from and dispatch to. If you use Redux, wrap the store in a plain JavaScript module that does not import from either framework. Both AngularJS services and React hooks can import this module. Another option is to use a reactive library like RxJS, which both ecosystems can consume.
3. The Seven-Step Workflow: From Audit to Cleanup
These steps are sequential. Skipping ahead usually forces you to backtrack.
Step 1: Audit and prioritize. List every AngularJS component, route, and service. Rank them by isolation (low dependency on other AngularJS features) and business value. Start with small, low-traffic widgets that you can replace in a sprint.
Step 2: Set up the shell and Module Federation. Create a new Webpack configuration that builds both your old AngularJS app and a new React micro-frontend. The shell loads the correct bundle based on the URL or a feature flag. Test that both frameworks can mount on the same page without crashing.
Step 3: Replace one component at a time. For your first candidate, write a React component that replicates the exact UI and behavior of the original AngularJS directive. Do not try to improve the design or add features during migration—that introduces risk. Keep the old directive in the codebase but hide it behind a feature flag. Run both versions in production for a week and compare error logs and performance metrics.
Step 4: Bridge the state. If the component reads from a shared service (like a user profile or a cart), refactor that service into the shared event bus or Redux store. Both the old AngularJS service and the new React component should use the same data source. This is the hardest step because it touches both codebases.
Step 5: Write integration tests for the hybrid page. Your tests should verify that the React component receives the correct props from the shell and that the AngularJS parts of the page still work. Use end-to-end tests that simulate user flows crossing the framework boundary.
Step 6: Remove the old directive. Once the React replacement has been running in production without errors for at least one release cycle, delete the AngularJS directive and its tests. Update any references in the build pipeline.
Step 7: Repeat until nothing is left. Continue replacing directives, controllers, and routes. When all user-facing components are React, migrate the remaining AngularJS services to plain JavaScript modules or React hooks. Finally, remove the AngularJS bundle from the build.
4. Tools, Setup, and Environment Realities
Your local development environment will need to run both frameworks simultaneously. That means a Node.js version that supports both AngularJS (which works on older Node versions) and the modern React toolchain. Use Docker to standardize the environment across the team, or use nvm to switch Node versions per project.
Webpack Module Federation in practice
The Module Federation plugin lets you expose modules from one build (the AngularJS app) to another (the React app) or vice versa. In practice, you configure two Webpack builds: one for the legacy app and one for the new React micro-frontend. The shell application loads both. A typical pitfall is mismatched versions of shared libraries (like React itself). Use the shared configuration in Module Federation to specify that React should be a singleton—only one copy of React will be loaded in the browser.
Feature flags for safe rollout
Do not rely on a single deploy to switch all users to the React version. Use a feature flag service (LaunchDarkly, Split, or even a simple cookie-based toggle) to enable the new component for a small percentage of users first. Monitor error rates and performance. If something breaks, you can disable the flag instantly without rolling back the entire deploy.
Performance monitoring
Running two frameworks on the same page increases the JavaScript bundle size and can degrade performance. Use Lighthouse or Web Vitals to measure the impact. If the React component is significantly slower than the AngularJS version, investigate why—often it is because the React component fetches data differently or re-renders too often. Optimize before rolling out to more users.
5. Variations for Different Team Constraints
Not every team has the same resources or risk tolerance. Here are three common scenarios and how to adapt the steps.
Small team, high risk aversion
If you have only two or three developers and the application is business-critical, do not use Module Federation initially. Instead, use an iframe for the first React component. The iframe isolates the React code completely, so a crash in the new component cannot break the AngularJS host. The downside is that communication between the two frames is limited to postMessage, and you lose URL integration. Use this approach only for truly standalone widgets (like a calculator or a form).
Large team, fast timeline
If you have a dozen developers and management wants the migration done in three months, consider a route-based migration instead of a component-based one. Each developer takes a full route (like /settings or /reports) and rewrites it entirely in React. The shell routes traffic to the new React route or the old AngularJS route based on a feature flag. This approach moves faster because you replace entire pages instead of individual components, but it also means you are rewriting more code at once and testing bigger chunks.
Hybrid: component-based migration with a shared design system
Many teams find that the component-based approach is safer but slower. To speed it up, create a shared design system (or use an existing one like Material-UI) that both AngularJS and React can consume. The AngularJS version of a button and the React version of the same button look identical and handle the same events. This way, when you replace a component, the visual result is exactly the same, and you do not need to update CSS or acceptance criteria.
6. Pitfalls, Debugging, and What to Check When It Fails
Even with careful planning, things will break. The most common failure modes are subtle and hard to reproduce.
Memory leaks from uncleaned watchers. AngularJS uses watchers to track changes. If you mount an AngularJS component inside a React component and the React component unmounts, the AngularJS watchers may not be destroyed. This causes memory to grow over time. The fix is to explicitly call $destroy() on the AngularJS scope when the React component unmounts. Use a useEffect cleanup function that triggers the destruction.
Race conditions between the digest cycle and React's reconciliation. If both frameworks update the DOM at the same time, you may see a flash of unstyled content or a brief layout shift. The solution is to ensure that React components do not read from AngularJS scope during the digest cycle. Use setTimeout or requestAnimationFrame to defer React updates until after the digest cycle completes.
Module Federation loading order. Sometimes the AngularJS bundle loads before the React bundle, and the shell cannot find the exposed module. Check the remotes configuration in your Webpack file and ensure the shared libraries are loaded in the correct order. Use the init method on the container to wait for all remotes to be available.
What to check first when a page crashes: Look at the browser console for errors about undefined providers or missing modules. If the error mentions AngularJS, the problem is likely in the bridge code. If the error mentions React, check that the shared state is being passed as props correctly. Also verify that the feature flag is set correctly—sometimes the flag flips for all users when you deploy a new environment variable.
After each migration step, run a full regression suite. Do not assume that because the React component works in isolation, it will work inside the AngularJS shell. Integration bugs are the hardest to catch and the most damaging to user trust.
Comments (0)
Please sign in to post a comment.
Don't have an account? Create one
No comments yet. Be the first to comment!