React Native's New Architecture: What Actually Changes When You Turn It On
React Native's New Architecture replaces the JSON bridge with direct synchronous native calls. Here's what Fabric, TurboModules, and bridgeless mode actually change, plus a safe migration order.
22 Jun 2026, 12:56 UTC

If your React Native app feels sluggish when JavaScript and native code talk to each other a lot—rapid scroll events, sensor streams, repeated synchronous reads—the bottleneck usually isn't your components. It's the bridge. The New Architecture (Fabric, TurboModules, JSI, and bridgeless mode) exists to remove that bottleneck, and it's mature enough that most teams should be planning the move. The catch: it's a dependency-compatibility project as much as a performance project.
The bridge was the tax on every native call
In the legacy architecture, JavaScript and native code communicate over an asynchronous bridge that serializes messages as JSON. Every call from JS into native—reading a device setting, updating a view, measuring layout—gets queued, serialized, shipped across, deserialized, and answered later. For occasional calls this is invisible. For high-frequency traffic it adds latency you can't optimize away in JS, and it forces patterns like batching and event throttling that complicate your code.
JSI (JavaScript Interface) replaces that indirection. It lets JavaScript hold direct references to native objects and call them synchronously, with no JSON serialization step. Everything else in the New Architecture builds on this.
The three pieces you'll actually interact with
Fabric is the new renderer. Because layout can be measured synchronously over JSI, UI updates that previously needed async round-trips (think tooltips that must position themselves against a measured element) become straightforward. It also unlocks concurrent React features like transitions and Suspense in native rendering.
TurboModules replace legacy Native Modules. The headline change is lazy loading: a module initializes on first use instead of at app startup. In an app that links dozens of native modules but touches a fraction of them on the launch path, this can shorten time-to-interactive noticeably. In a small app with three modules, you may not see much.
Bridgeless mode removes the legacy bridge entirely rather than running both systems side by side. This is the end state of the migration, and in recent releases it has become the default for new projects—though the exact release where defaults flipped is version-sensitive, so check the template your CLI generates rather than trusting any article, including this one.
Codegen is the part that changes how you write native modules
Legacy modules were informal: you exposed methods, and the bridge figured it out at runtime. TurboModules require a typed spec—TypeScript or Flow—that Codegen turns into type-safe native bindings. Mismatches become compile errors instead of runtime surprises, which is a genuine improvement, but it means custom native modules need to be rewritten against the spec contract.
Here's the shape of a minimal synchronous TurboModule. First the spec, in NativeDeviceSettings.ts:
import type {TurboModule} from 'react-native';
import {TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
getSystemLocale(): string;
isLowPowerModeEnabled(): boolean;
}
export default TurboModuleRegistry.getEnforcing<Spec>('NativeDeviceSettings');Codegen reads this file and generates the native interface your Android and iOS implementations must satisfy. The JS call site becomes a plain synchronous call—no callbacks, no promises for a value that's already on the device:
import NativeDeviceSettings from './NativeDeviceSettings';
const locale = NativeDeviceSettings.getSystemLocale();Compare that to the legacy equivalent, where even a trivial read went through an async callback or promise purely because the bridge couldn't do synchronous returns. Run Codegen via your normal build (it's wired into the Gradle and CocoaPods steps in supported versions) and confirm the generated bindings compile on both platforms before writing any real logic—platform drift here is a common early stumble.
Adopt it incrementally, and audit dependencies first
You don't have to migrate everything at once. The interoperability layer lets many legacy modules keep working under the New Architecture while you port the ones you own. The realistic migration order is:
- Audit every third-party native dependency against its documented New Architecture support. This is the step that decides your timeline.
- Enable the New Architecture in a development build and run your full regression suite, paying attention to libraries that assume bridge behavior—some fail silently rather than crashing.
- Port your own native modules to TurboModule specs one at a time.
- Enable bridgeless mode last, once nothing in your dependency tree relies on the bridge.
To check whether a fresh project already has it enabled, create one with the current React Native CLI and inspect the generated config: on Android look at newArchEnabled in gradle.properties; on iOS check how the Pods are set up in the Podfile. Don't assume—defaults have shifted across releases.
The honest trade-off
Performance gains are workload-dependent. If your app crosses the bridge rarely—mostly static screens, few native calls—you may see little user-visible difference, and the migration cost will outweigh the benefit for now. The wins concentrate in apps with heavy native traffic, many lazily-loadable modules, or UI that needs synchronous layout. Also expect some tooling churn: debugging workflows built around older tools have changed alongside the architecture, so budget time for your team to relearn inspection and profiling habits.
A practical next step
Don't benchmark someone else's app—benchmark yours. Toggle the New Architecture on and off in the same build, then measure two things: cold startup time, and the latency of your most frequent JS-to-native call under realistic load. If the numbers move, migrate. If they don't, keep it on your roadmap but spend the effort elsewhere. Either way, run the dependency audit now; knowing which library is your blocker is useful long before you're ready to flip the switch.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.