The React Native New Architecture migration: what breaks and what doesn't
The New Architecture has been the React Native default since 0.76 (October 2024). Fabric, TurboModules, and Bridgeless mode replace the old renderer, native-module system, and async bridge.
The interop layer covers most legacy native modules. It doesn't cover libraries that reached into bridge internals, did custom event dispatch, or relied on async serialization quirks. Those break on first launch.
Most of the migration effort isn't in your app code. It's in auditing third-party libraries, replacing the dead ones, and rebuilding any custom native modules against the codegen-driven spec format.
You upgrade React Native past 0.76. The build succeeds. The app launches. Then a screen renders blank, a gesture stops working, or a native module silently no-ops. Welcome to the New Architecture.
This isn't a feature you opt into anymore. It's the platform. The work isn't deciding whether to migrate. It's mapping which parts of your code, your dependencies, and your native modules need attention before you ship.
- What the New Architecture actually is
- The three pieces: Fabric, TurboModules, Bridgeless
- The interop layer and what it covers
- Audit: what to look at first
- Third-party libraries: the long pole
- Your code: what to change
- Custom native modules: the real work
- Common failure modes
- Performance: what you actually get
- FAQ
What the New Architecture actually is
The "old" React Native architecture had a bridge. JavaScript and native code lived in separate worlds. They exchanged messages by serializing arguments to JSON, queuing them on the bridge, and processing them asynchronously on the other side.
That bridge was the source of half the framework's problems. Every native call was async. Every layout update had to round-trip through serialization. Animations driven from JS could stutter because the bridge was busy. Lists rendered slowly because the shadow tree was reconciled across threads with serialization in between.
The New Architecture removes the bridge. JavaScript and native share memory through JSI (the JavaScript Interface). Native modules can be called synchronously when it makes sense. The renderer (Fabric) reads the shadow tree directly without crossing a serialization boundary.
It's been in development since 2018. It became opt-in in 0.68. The interop layer landed in 0.74. It flipped to default-on in 0.76 in October 2024. The legacy bridge is on a deprecation path — it's still there in 0.76 and 0.77 if you opt out, but it won't be there forever.
The three pieces: Fabric, TurboModules, Bridgeless
Three components, named separately, often confused.
| Piece | What it replaces | What it gives you |
|---|---|---|
| Fabric | The old renderer (UIManager, paper) | Direct shadow-tree access, concurrent rendering primitives, fewer cross-thread hops |
| TurboModules | NativeModules (the old async-only system) | Type-safe specs, lazy loading, sync calls where appropriate, codegen-driven bindings |
| JSI / Bridgeless | The asynchronous JSON bridge | Direct C++ binding between JS engine and native, no serialization round-trips |
You don't migrate to them individually. They're enabled together by a single flag (newArchEnabled in Android gradle.properties and the iOS Podfile). When the flag is on, you get all three. When it's off, you get all three of the legacy versions.
The interop layer and what it covers
The interop layer is the bridge between the old world and the new. It lets a legacy native module — written against the old NativeModule API — keep working when the rest of the runtime is using TurboModules.
What it covers:
- NativeModule classes that extended
RCTBridgeModule(iOS) orReactContextBaseJavaModule(Android) and used standard method exports - ViewManager classes that registered views the standard way
- Event emitters that used
RCTEventEmitter - Most callback and promise patterns
What it doesn't cover:
- Modules that called
RCTBridgeprivate APIs directly - Custom event dispatch that bypassed
RCTEventEmitter - Code that relied on the bridge being a singleton (it isn't, under Bridgeless)
- Modules that depended on the bridge's async serialization behavior — for example, code that assumed two consecutive calls would arrive in order
- Anything that used
RCTRootViewdirectly with custom configuration
The first three items are the common cases. The other two trip up older libraries that were doing clever things under the hood.
Interop coverage has improved with every minor release. A library that didn't work under New Arch in 0.74 may work in 0.76 without any changes from the maintainer. Re-test on the React Native version you're targeting before declaring a library incompatible.
Audit: what to look at first
Before flipping newArchEnabled, do a paper audit.
- List every third-party library in
package.jsonthat has native code. Anything that runspod installwork or has anandroid/folder under the package counts. - For each, check New Architecture status. The React Native Directory has a New Arch column. The library's README usually says. Look for
codegenConfigin itspackage.json— that's the giveaway that the library has been migrated. - List every custom native module in your repo. Files in
ios/MyApp/that extendRCTBridgeModuleorRCTViewManager. Files inandroid/app/src/main/java/that extendReactContextBaseJavaModuleorSimpleViewManager. - List every
patch-packagepatch you have against native modules. Patches against native files are red flags. They may be patching the very thing the New Architecture changed.
You're not deciding anything yet. You're enumerating the surface area. The audit tells you which decisions are real.
Third-party libraries: the long pole
For most teams, the third-party library list is where the migration lives. Each library falls into one of four buckets.
Migrated and current
Reanimated 3, React Native Gesture Handler 2.x, Skia, React Navigation, FlashList, Expo modules — the popular libraries have New Architecture support and have shipped it. Upgrade to a current version, you're done.
Check the changelog for the version that added New Arch support. That's your minimum.
Works via interop, no migration needed
Libraries that use the standard NativeModule and ViewManager APIs without doing anything fancy. They work under the New Architecture through the interop layer with no changes.
You don't have to do anything except verify behavior in your app. The library author may eventually publish a proper migration; you don't have to wait for it.
Maintained, migration in progress
An open PR, a beta version with New Arch support, an issue thread where the maintainer is actively working on it. Install the beta, test, contribute back if you find issues.
This is also the bucket where you fork most often. Apply the PR locally, publish under your scope, swap your dependency over. Keep a note to switch back when the upstream merges.
Abandoned
The last commit is 18+ months old. The issue tracker is full of New Arch reports. The maintainer hasn't responded in a year.
You replace, fork, or absorb. Replacement is best. Forking buys time. Absorbing — pulling the library's logic into your own native module — is what you do when the library was tiny and you've been planning to remove it anyway.
Your code: what to change
App-level JavaScript changes are usually minimal. The New Architecture preserves the React component API. Your View, Text, FlatList, and hooks all keep working.
The places you touch:
- Enable the flag. Android:
newArchEnabled=trueinandroid/gradle.properties. iOS: setRCT_NEW_ARCH_ENABLED=1when runningpod install(or viaENV['RCT_NEW_ARCH_ENABLED'] = '1'in the Podfile). - Run codegen. Once enabled, the build runs
codegento generate the native spec files from your library declarations. The first run is the noisy one. Fix any spec errors it reports. - Update any direct bridge access. If your JS calls
NativeModules.SomeModule.method()with no spec, you'll get a runtime warning. The fix is wrapping it in a typed module declaration. - Find-replace deprecated APIs. A few JS-side APIs were deprecated alongside the New Architecture. The migration guide lists them. The big one is anything calling
UIManagercommands directly — those need acodegenNativeCommands-style wrapper now.
For most apps that's it. The architecture switch is mostly transparent at the React component level. The pain is in native code and dependencies.
Custom native modules: the real work
If your app has custom native modules — and most mature apps do — this is where the time goes.
The TurboModule migration is structural. You write a spec file in TypeScript or Flow that declares the module's interface. Codegen generates the native interfaces from the spec. You implement those interfaces in Objective-C/Swift on iOS and Java/Kotlin on Android.
What a minimal spec looks like:
You then add a codegenConfig block to your library's package.json pointing at the spec. The build runs codegen, generates the iOS protocol and Android interface, and you implement them.
What's different from the old way:
- Types are enforced at the boundary. You can't quietly pass a string where a number was expected.
- Methods are typed as sync, async, or promise. You declare it in the spec; codegen wires it correctly.
- Constants are exposed through a single typed method instead of
constantsToExport. - Module instantiation is lazy. The module isn't loaded until first call.
For a module that was 50 lines, the migration is a couple of hours. For a module that's 2,000 lines, has its own threading model, and ships a custom event dispatcher, it's days to weeks.
Common failure modes
Patterns we see repeatedly during New Architecture migrations.
- Blank screen on first launch. Usually a view manager that didn't migrate cleanly. The component renders, but Fabric can't reconcile it. Check the Metro logs for a "no ComponentDescriptor found" warning.
- "Module cannot be null" at startup. A TurboModule spec references a module that wasn't registered. Either the native implementation is missing, or the package install step didn't run after codegen regenerated.
- Animations stutter where they used to be smooth. Almost always Reanimated still on v2 under the new runtime. Upgrade to v3.x and re-test.
- Gesture handler events stop firing. RNGH versions older than 2.14 don't reliably work under New Arch. Upgrade.
- Image components flicker on update. A FastImage or similar library that hasn't migrated. The interop layer handles it, but its event lifecycle isn't quite right under Fabric.
- Builds work, but app crashes on physical iPhone only. Often a Hermes vs JSC mismatch. New Architecture defaults to Hermes; if you'd been using JSC and never reconfigured, some library is incompatible.
- Android release build fails with R8 / ProGuard rule errors. Codegen generates classes R8 doesn't know to keep. Add keep rules for
com.facebook.react.turbomodule.**and your generated specs.
Performance: what you actually get
The framing around New Architecture performance has been generous. Here's what teams actually see:
- Cold start: 10-25% improvement on Android, less on iOS. Mostly from lazy TurboModule loading.
- List scroll performance: noticeable wins on long, complex lists. The renderer doesn't round-trip through serialization for layout changes.
- Gesture responsiveness: meaningful improvement. The big one. Synchronous native calls remove the dropped-frame failure mode for fast gestures.
- Animation smoothness: depends entirely on whether you were already using Reanimated or driving animations from the JS thread. If you were using Reanimated and the native driver, you won't notice. If you were animating from JS, you'll see a dramatic improvement.
- Memory: mostly a wash. Sometimes slightly worse on iOS, slightly better on Android.
The biggest wins are in apps that were bottlenecked on bridge traffic. Apps that had already been tuned around the old bridge — using InteractionManager, batching updates, keeping JS thread work minimal — won't see a dramatic difference.
None of this is the headline reason to migrate. The headline reason is: the legacy architecture is on a deprecation path. You're migrating to stay on a supported React Native release.
Behind on the New Architecture move?
The migration is rarely the engineering team's biggest worry — until they're three React Native versions behind and the dependency graph has compounded. Run the free scanner against your lockfile to see which libraries are blocking the upgrade.
Frequently Asked Questions
What is the React Native New Architecture?
The New Architecture is the umbrella name for three pieces of plumbing under React Native: Fabric (a rewritten renderer), TurboModules (a new native-module system), and JSI plus Bridgeless mode (the JavaScript–native interface that replaces the old async bridge). It removes the serialization layer between JS and native, lets native modules be called synchronously, and gives the renderer direct access to the shadow tree. It's been the default since React Native 0.76 (October 2024).
Do I have to migrate to the New Architecture?
If you upgrade past React Native 0.76 it's on by default. You can opt out for one or two more versions with the newArchEnabled flag, but support for the legacy bridge is being removed. Treat the migration as the cost of staying on a current React Native release. The longer you wait, the more your dependency graph drifts from the libraries that have already moved on.
What is the React Native interop layer?
Interop layer is the shim that lets legacy native modules and view managers work under the New Architecture without being rewritten. It was added in 0.74 and improved through 0.76. It covers most legacy code that uses the old NativeModule and ViewManager APIs. It doesn't cover everything — modules that called into private React Native internals, used custom event dispatch, or did anything fancy with the bridge will still break.
How do I check if a library supports the New Architecture?
Three places to look. First, the library's README or its package.json — look for codegenConfig or a "newArchSupport" note. Second, the React Native Directory at reactnative.directory, which has a New Architecture compatibility column. Third, the library's GitHub issues — search for "TurboModule" or "Fabric". If a library is unmaintained and isn't listed as compatible, plan to replace it or contribute the migration upstream.
How long does a New Architecture migration take?
For a small app with mostly first-party libraries, a few days to a week. For a medium app with 30–50 third-party libraries, two to six weeks once you account for replacing or forking the libraries that haven't migrated. For an enterprise app with custom native modules, the native-side rewrite is the long pole — anywhere from a month to a quarter depending on how the modules were originally written.
Will the New Architecture make my app faster?
Yes, but probably not as much as the marketing implies. Synchronous native calls remove the bridge serialization round-trip, which speeds up gesture handlers, list rendering, and complex layouts. Cold start time also improves. Apps that were already performant won't see a dramatic difference. Apps that bottlenecked on bridge traffic — heavy native module calls, complex animations driven from JS — will see real wins.