From Bridge to JSI: Deciding When to Migrate to React Native's New Architecture
How JSI, Fabric and TurboModules replace the old React Native bridge, what the reported gains mean in practice, and how to audit and migrate an existing app safely.
This article is published in English.
Sources: React Native’s New Architecture Finally Stopped Being a Compromise
Anyone who has shipped a non-trivial React Native app on the legacy architecture has seen a fast scroll stutter the moment the JavaScript thread gets busy with something else, such as parsing a network response or propagating a state change through a large component tree. That stutter was not bad luck; it was a direct consequence of how the old bridge worked. The New Architecture that replaces it has been the default since React Native 0.76, but for a long time many teams treated it as something to adopt later. This article explains what the three pieces of the New Architecture actually change, how to read the performance claims around it, and how to decide whether and how to migrate an existing app.
Why the old bridge caused jank
On the legacy architecture, every interaction between JavaScript and native code went through an asynchronous bridge. A call was serialized to JSON, queued on the bridge, deserialized on the native side and executed there, with results travelling back the same way. Two things follow from this design. Every crossing paid a serialization cost, and because the bridge was asynchronous and ordered, anything headed for native code had to wait whenever the JavaScript thread was occupied. A busy JavaScript thread therefore stalled scrolling, layout and gestures that depended on it.
The replacement has existed in some form for years, and it has officially been the default since 0.76. Being the default and being something a cautious team will bet a production app on are different claims, though. The releases in the first half of this year, React Native 0.85 followed by Expo SDK 56, are widely seen as the point where that gap closed; the specifics are covered further down.
The three pieces of the New Architecture
Leaving marketing aside, the New Architecture consists of three components that work together, each replacing a specific part of the bridge.
JSI: direct calls instead of messages
The JavaScript Interface (JSI) is the foundation. It is a lightweight C++ interface through which JavaScript can hold references to native objects and call native functions directly, including synchronously when that makes sense. There is no JSON serialization, no bridge queue and no asynchronous round trip for calls that do not need one. The old path of serializing, queueing, deserializing and dispatching collapses into what is essentially a C++ function call.
Fabric: a shared C++ shadow tree
Fabric is the rendering system built on JSI. It organizes rendering into three layers: the React fiber tree in JavaScript, a shadow tree, and the tree of native host views. The crucial difference is that the shadow tree now lives in shared C++ code instead of being driven through the bridge from JavaScript. Layout with Yoga is computed in that shared layer, so layout passes no longer bounce across the bridge.
Fabric is also what makes concurrent React features, such as transitions, Suspense and useTransition, work on React Native at all. Its commit phase can run synchronously or concurrently, and the bridge-based renderer had no way to support either mode properly. If your team wants the same concurrent rendering model it uses on the web, Fabric is a prerequisite rather than an optimization.
TurboModules: lazy, typed native modules
TurboModules replace the legacy NativeModules system for accessing native functionality. The snippet contrasts the two approaches. The old import pulls a module off NativeModules, which were initialized eagerly at startup and paid the bridge's serialization cost on every call. The new import loads a module generated from a typed spec, backed by JSI, initialized only when first used and callable synchronously where appropriate.
// Old NativeModules: loaded eagerly at startup, whether used or not,
// with every call paying the bridge's serialization cost
import { NativeModules } from 'react-native';
const { CameraModule } = NativeModules;
// TurboModules: JSI-backed, lazily loaded only when actually called,
// with type-safe interfaces generated by Codegen from your spec
import CameraModule from './specs/NativeCameraModule';
CameraModule.capturePhoto(); // direct, synchronous-capable call via JSI
The lazy loading matters more than it first appears. On the legacy system, an app with many native modules paid an initialization cost at startup for each one, even if a given session never touched most of them. TurboModules only pay when a module is actually used. The typed spec is the other half of the benefit: Codegen turns it into native interfaces, which is covered in detail in our article on why Codegen makes spec-first mandatory for TurboModules.
Reading the performance claims carefully
Numbers quoted for the New Architecture differ by source and by workload, so treat them as a direction rather than a figure to promise stakeholders. Reported results include:
- roughly 10 to 30% better UI-thread performance for typical apps, reaching up to 3x for apps that rely heavily on native modules
- 20 to 50% faster calls from JavaScript into native code
- 20 to 40% shorter startup times, depending on how many native modules an app has and which operations are measured
A more useful lens than any single percentage is which categories of app moved from possible-with-workarounds to straightforward. Camera-heavy apps, real-time data visualization, complex animated interfaces and anything that needs genuinely synchronous layout measurement either required elaborate architectural tricks on the bridge or were not practical at all. On the New Architecture they are ordinary engineering work. If your app falls into one of those categories, the case for migrating is strong regardless of the average numbers; if it is mostly forms and lists, the gains will be real but more modest. The only numbers that should drive your decision are the ones you measure on your own app before and after.
What the recent release cycle fixed
The reason this year is considered a turning point is not a new capability but a set of fixes to the specific concerns that kept cautious teams waiting. Check the official release notes for exact details, since the summary below reflects reports at the time of writing.
- React Native 0.85, released in April, rebuilt the shared animation system and removed a long-standing source of dropped frames. Animation-heavy transitions are reported to hold 60 fps on most supported devices instead of degrading under load.
- The same release fixed a significant group of memory leaks on both Android and iOS that made long-running sessions progressively sluggish. This kind of slow degradation appears only after an app has been open for a long time, which makes it hard to catch in normal QA and hard to diagnose from user reports that the app gradually starts behaving oddly.
- Expo SDK 56, which followed in May, is reported to bring local builds of mid-sized apps down from 110 to 120 seconds to under 50, and to cut the time to publish over-the-air updates by about a third. For teams that ship daily, those savings accumulate quickly.
- React 19.2 integration adds benefits for teams using React Native Web or hybrid apps. Updates to server-side Suspense and Web Streams let more of the UI be sent early with less main-thread blocking, which helps the web-facing part of the ecosystem that release notes often underemphasize.
Migration in practice: the dependency audit comes first
None of this means every existing app should switch tomorrow. The number to check before committing is library compatibility. At the time of writing, about 85% of popular React Native npm packages are reported to support the New Architecture, leaving roughly 15% that are bridge-only or only partly compatible. Stalled migrations almost always get stuck in that remainder: not in the framework itself, but in a few third-party native modules the app depends on.
A pattern that recurs among teams that migrate successfully looks like this:
- Audit before you start. List every native dependency and verify its New Architecture support against its current release, not against what you remember from a year or two ago. For each unsupported package, decide whether to upgrade, replace, fork or wrap it yourself.
- Lean on Codegen. Because Codegen produces type-safe native interfaces from your TypeScript or JavaScript specs, a whole class of mismatches between JavaScript and native code is caught at build time instead of surfacing as runtime crashes. If you write your own modules, our walkthrough on wiring an in-app Turbo Module end to end with Codegen shows the full flow.
- Migrate incrementally. Rather than a single big-bang switch, work on a feature branch and move one module or screen at a time, validating each step instead of staking the whole app on one migration event.
- Test long sessions. Given the class of bugs recent releases fixed, include soak tests that keep the app open and active for an extended period, and compare memory and frame rates before and after.
Guidance by situation
Starting a new project
There is little reason to opt out. The New Architecture is the default, the surrounding tooling, Expo SDK 56 in particular, assumes you are on it, and the compatibility gap is now small enough that you are unlikely to hit a wall unless the app has unusual native dependencies.
Maintaining an app on the legacy bridge
Begin with the compatibility audit before touching anything else, using your real dependency list. The share of compatible packages has been rising steadily, so an assessment from a year ago is probably out of date. Plan an incremental migration, and treat the errors Codegen surfaces as a genuine safety net during the transition rather than as ceremony.
Teams that evaluated it earlier and decided to wait
Postponing in 2023 or 2024 was a reasonable decision given the rough edges at the time. The animation rebuild and memory-leak fixes in 0.85 target precisely the production-stability concerns that justified waiting, which makes this release cycle the natural moment to reopen the question.
Key takeaways
- The legacy bridge serialized every call and queued it behind a busy JavaScript thread; that, not bad luck, is where much historical jank came from.
- JSI provides direct calls, Fabric moves the shadow tree and layout into shared C++ and enables concurrent React, and TurboModules load lazily from typed specs.
- Published speed-ups vary widely; decide based on your app's category and your own measurements.
- What changed recently is stability and tooling, not features: animation reliability, long-session memory behavior and build speed.
- Migration risk sits in third-party native modules, so audit dependencies first, rely on Codegen and move one screen or module at a time.