Migrations · New Architecture

The React Native New Architecture migration: what breaks and what doesn't

Published April 29, 2026 · 13 minute read
TL;DR

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.

On this page
  1. What the New Architecture actually is
  2. The three pieces: Fabric, TurboModules, Bridgeless
  3. The interop layer and what it covers
  4. Audit: what to look at first
  5. Third-party libraries: the long pole
  6. Your code: what to change
  7. Custom native modules: the real work
  8. Common failure modes
  9. Performance: what you actually get
  10. 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.

PieceWhat it replacesWhat it gives you
FabricThe old renderer (UIManager, paper)Direct shadow-tree access, concurrent rendering primitives, fewer cross-thread hops
TurboModulesNativeModules (the old async-only system)Type-safe specs, lazy loading, sync calls where appropriate, codegen-driven bindings
JSI / BridgelessThe asynchronous JSON bridgeDirect 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:

What it doesn't cover:

The first three items are the common cases. The other two trip up older libraries that were doing clever things under the hood.

Practical note

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.

  1. List every third-party library in package.json that has native code. Anything that runs pod install work or has an android/ folder under the package counts.
  2. For each, check New Architecture status. The React Native Directory has a New Arch column. The library's README usually says. Look for codegenConfig in its package.json — that's the giveaway that the library has been migrated.
  3. List every custom native module in your repo. Files in ios/MyApp/ that extend RCTBridgeModule or RCTViewManager. Files in android/app/src/main/java/ that extend ReactContextBaseJavaModule or SimpleViewManager.
  4. List every patch-package patch 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.

Bucket 01

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.

Bucket 02

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.

Bucket 03

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.

Bucket 04

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:

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:

// MyModuleSpec.ts import type { TurboModule } from 'react-native'; import { TurboModuleRegistry } from 'react-native'; export interface Spec extends TurboModule { getDeviceInfo(): Promise<{ name: string; osVersion: string }>; trackEvent(name: string, properties: Object): void; } export default TurboModuleRegistry.getEnforcing<Spec>('MyModule');

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:

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.

Performance: what you actually get

The framing around New Architecture performance has been generous. Here's what teams actually see:

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.