Technology · Mobile Development
React Native's New Architecture: Fabric and JSI in 2026
React Native's Fabric and JSI replaced the async bridge with direct native calls, default since RN 0.76. What changed, what migration costs, and the rough edges left.
Anurag Verma
8 min read
Sponsored
React Native had a reputation problem that was hard to argue with. The bridge (the async, JSON-serialized communication layer between JavaScript and native code) was a genuine bottleneck. Every call from JS to a native module went through serialization, crossed the bridge asynchronously, got deserialized on the other side, and returned the same way. For common operations like scroll events, animations, or continuous gestures, this created visible jank that developers spent years working around with tools like react-native-reanimated running directly on the UI thread.
The new architecture (Fabric renderer plus JSI plus TurboModules) addressed this at the runtime level. It’s been in development since 2019, became opt-in stable in RN 0.74, and is the default in 0.76 and beyond. If you started a new project any time in the past year, you’re already on it. If you’re maintaining an older app, you need a migration plan.
Here’s what actually changed, what the migration took, and where the remaining friction is. (If you’re still deciding whether React Native is the right cross-platform bet at all, see React Native vs. Flutter vs. PWA first.)
What the Old Bridge Was Doing Wrong
The old architecture had a single JS thread, a native thread, and a shadow thread for layout. Communication between them went through the asynchronous bridge. Every setState triggered a render, which triggered a shadow tree diff in JavaScript, which serialized a description of changes across the bridge, which got applied to the native view hierarchy.
This caused specific, reproducible problems:
Gesture lag. If a user scrolled fast and the JS thread was busy processing other work, scroll events would queue up behind the existing work. The native side knew the user was scrolling but couldn’t get updated position data from JS quickly enough. You’d feel the delay.
Synchronous native calls were impossible. Calling a native module returned a Promise. There was no way to call native code and get a result back in the same JS execution frame. This blocked patterns like reading a layout value and immediately making a decision based on it.
Animation stutter. Any animation that needed JS to update a value on each frame would stutter whenever the JS thread was busy. This happened during hydration, during heavy state updates, and on lower-end Android devices where JS execution was simply slower.
What JSI Changed
JSI replaces the bridge with a direct JavaScript-to-C++ host object interface. Instead of serializing arguments and posting them to a queue, JavaScript can call native C++ functions directly. The objects are shared across the JS and native runtimes, with no serialization and no queuing.
The practical result:
- Synchronous native calls are now possible. If you need to read a native value and use it immediately, you can.
- Native code can hold a reference to a JS object and call back into it later without going through the bridge event system.
- Reanimated and Gesture Handler can share a common worklet runtime with the native UI thread, enabling 60/120fps animations that are immune to JS thread congestion.
JSI itself is just the interface layer. TurboModules are the actual native modules rebuilt to use JSI instead of the bridge. Fabric is the new rendering pipeline that uses JSI to synchronize the React fiber tree with native views.
What Fabric Changed
Fabric replaced the UIManager bridge pattern with a three-layer rendering system: React fiber tree (JS), Shadow tree (shared C++ layer), and Host view tree (native platform views).
The Shadow tree now lives in C++, not in JavaScript. This means layout calculation (via Yoga) happens in the shared C++ layer rather than requiring JS→bridge→native round trips. The commit phase (where React confirms a render is complete and the native side should update) can now happen synchronously or concurrently.
This is what makes Concurrent React work on React Native for the first time. React 18’s concurrent features, startTransition, Suspense, and concurrent rendering (the same primitives covered in our React 19 guide for web), require the ability to interrupt and resume rendering. That wasn’t possible with the old bridge. With Fabric’s synchronous commit capability, they work correctly.
import { startTransition, useState } from 'react'
import { FlatList, TextInput } from 'react-native'
function SearchScreen({ items }) {
const [query, setQuery] = useState('')
const [filtered, setFiltered] = useState(items)
const handleChange = (text) => {
setQuery(text)
// Mark the list update as non-urgent
// The input stays responsive even on large lists
startTransition(() => {
setFiltered(items.filter(i => i.name.includes(text)))
})
}
return (
<>
<TextInput value={query} onChangeText={handleChange} />
<FlatList data={filtered} renderItem={({ item }) => <ItemRow item={item} />} />
</>
)
}
On the old architecture, this search pattern would drop input frames on large lists. On the new architecture with startTransition, the input update is committed synchronously while the list re-render yields.
The Migration Path for Existing Apps
If you’re migrating an existing app to the new architecture, the process depends on how many native modules you have and whether they’re community packages or custom code.
Step 1: Audit your native modules. Run npx react-native info and look at your installed packages. Any package that uses native code needs to be compatible with the new architecture (either interop-layer compatible or TurboModule-ready). The React Native Directory (reactnative.directory) shows architecture compatibility for community packages.
Step 2: Enable the interop layer. Since RN 0.74, an interop layer allows old-architecture native modules to work with the new rendering pipeline without requiring the library authors to rewrite everything. Enable it in your project:
// metro.config.js
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config')
const config = {
resolver: {
unstable_enablePackageExports: true,
},
}
module.exports = mergeConfig(getDefaultConfig(__dirname), config)
// AppDelegate.mm — enable new arch
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.moduleName = @"YourApp";
self.initialProps = @{};
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
The interop layer buys you time to migrate dependencies without blocking the new architecture adoption.
Step 3: Update or replace incompatible packages. If a package doesn’t work with the interop layer (some don’t), your options are: find an alternative package, fork and update it, or rewrite the affected functionality using Expo Modules API, which creates new-architecture-ready native modules with much less boilerplate.
Step 4: Enable new architecture in Gradle and Podfile.
For Android (android/gradle.properties):
newArchEnabled=true
For iOS (ios/Podfile):
# This is now the default since RN 0.76, but for older projects:
ENV['RCT_NEW_ARCH_ENABLED'] = '1'
Run pod install after updating the Podfile.
What the Migration Actually Cost Teams
The teams that had the smoothest migrations were running relatively modern dependency stacks. Apps that had avoided native module sprawl and were using well-maintained packages from the React Native ecosystem (React Navigation, React Native MMKV, React Native Reanimated, Expo modules) found that most of their dependencies were already new-architecture compatible.
Apps with custom Objective-C or Java native modules required more work. The modules needed to be rewritten to conform to the TurboModules spec or wrapped in compatibility shims. A rough estimate: one custom native module takes 1-2 days to migrate, assuming a developer who understands the platform. That migration time is worth budgeting for explicitly; see our mobile app cost breakdown for how teams tend to underprice this kind of platform-maintenance work.
The most common blockers in order:
- Legacy native modules that the library author hadn’t updated and had no interop shim
- Custom native views that needed to be rewritten as Fabric components
- Assumptions in custom code about the bridge event system that didn’t hold under JSI
Where the Rough Edges Remain
The new architecture is better. Performance improvements on scroll-heavy and animation-heavy screens are visible on real devices. But it’s not finished work.
Codegen warnings. The new architecture uses Codegen to generate type-safe native bindings from your TypeScript definitions. If your prop types aren’t exactly what Codegen expects, you’ll get warnings or silent failures. The error messages are better than they were but still often don’t point directly at the problem.
Debug experience on Android. The debugger for the Hermes engine (used on the new architecture) is solid on iOS. On Android, the Metro bundler and DevTools connection is reliable, but the native-side debugging story for TurboModules is still rougher than you’d want.
Third-party package ecosystem. The interop layer saved most packages from needing immediate rewrites, but interop has limits. Some edge cases, particularly around view commands and event emitters, don’t go through the interop layer correctly.
New Projects: Just Use Expo
For any new React Native project in 2026, start with Expo. The managed workflow handles the new architecture by default, Expo Router gives you file-based navigation out of the box, Expo Modules provides a clean TypeScript API for writing new-architecture native code when you need it, and the ecosystem integrations are built and tested against the current architecture.
The “bare React Native vs Expo” debate has moved substantially toward Expo for most use cases. You can eject to bare workflow if you have requirements the managed workflow can’t meet. But starting bare to “have more control” means taking on maintenance work before you know you need that control.
The new architecture is a real improvement. For animation performance, gesture handling, and concurrent feature support, it delivers what it promised. The migration cost was real but bounded. Most production apps in the React Native ecosystem are now through it.
Frequently asked questions
- Is React Native's new architecture stable in 2026?
- Yes. Fabric, JSI, and TurboModules went opt-in stable in RN 0.74 and became the default in RN 0.76. Most production React Native apps have migrated or are running on it by default for new projects.
- Do I need to migrate an existing React Native app to the new architecture?
- Eventually, yes. The interop layer lets old-architecture native modules keep working for now, but it doesn't cover every case, particularly view commands and event emitters, and the ecosystem is converging on new-architecture-only packages over time.
- How long does migrating to React Native's new architecture take?
- It depends on your native module footprint. Apps built mostly on well-maintained community packages (React Navigation, React Native MMKV, Reanimated, Expo modules) migrate quickly since those are already compatible. Custom native modules take roughly 1-2 days each to rewrite for the TurboModules spec.
- Should a new React Native project use Expo or bare React Native?
- Use Expo. The managed workflow handles the new architecture by default, Expo Modules gives you a clean TypeScript API for custom native code, and the ecosystem is built and tested against the current architecture. Starting bare for 'more control' means taking on migration and maintenance work you don't need yet.
Sponsored
Sponsored
Discussion
Join the conversation.
Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.
Sponsored