How Does React Native Work? The Architecture Explained (JSI, Fabric, TurboModules, Hermes)

How React Native works in 2026: Hermes runs your JS, JSI connects it to C++, Fabric renders native views, TurboModules call native code. No bridge left.

How Does React Native Work? The Architecture Explained (JSI, Fabric, TurboModules, Hermes)

How does React Native work? Your components are written in JavaScript, run by a JavaScript engine inside the app, and React Native turns the tree they describe into real platform views: UIView on iOS, android.view.View on Android. No WebView, no HTML. Between the JavaScript and the native side sits a C++ layer that lets the two call each other directly. That is the whole idea in three sentences. The rest of this post is what happens inside those sentences, as of React Native 0.88 (with 0.89 in release candidate), and why it matters when your app stutters.

I am writing this for developers who already ship React Native and want the mental model, not the marketing. If you are still choosing a stack, start with React vs React Native and Flutter vs React Native, then come back.

The three parts of every React Native app

  1. Your JavaScript: React components, state, business logic, and libraries, bundled by Metro and executed by Hermes, the JavaScript engine Meta built for React Native.
  2. The native host: a normal iOS or Android app with an AppDelegate or MainActivity, the platform’s view system, and any native modules (camera, storage, Bluetooth) you or your libraries add.
  3. The layer in between: C++ code shared by both platforms. JSI (the JavaScript Interface) lets JavaScript hold references to C++ objects and call them; Fabric (the renderer) turns React’s output into native views; TurboModules expose native modules to JavaScript; Yoga computes Flexbox layout.

Parts 2 and 3 are why a React Native app looks and scrolls like a native app. Part 1 is why you can share most of your code between platforms and update logic without rewriting it twice.

From the bridge to JSI: a short history that explains a lot

For its first seven years, React Native had a bridge. JavaScript and native code lived in separate worlds and talked by sending batched, asynchronous, JSON-serialised messages across it. It worked, and it is why React Native existed at all, but it had three costs you could feel: every crossing paid for serialisation, nothing could be synchronous (so measuring a view or reading a value meant a round trip), and a busy bridge meant laggy gestures and blank cells in long lists.

The New Architecture replaced it. It became the default in 0.76 (October 2024), and in 0.82 (October 2025) it became the only architecture: setting newArchEnabled=false is simply ignored. The 0.89 release candidate goes further and deletes the leftover bridgelessEnabled flags and the old RCTNativeAnimatedModule on iOS. Interop layers remain so older libraries keep working, but if you read a 2021 blog post that explains React Native through “the bridge”, it is describing an app you can no longer build.

Hermes: where your JavaScript runs

Hermes compiles your JavaScript to bytecode at build time, so the app ships bytecode instead of source and skips parsing on startup. That is the main reason React Native cold starts improved so much over the last few years. Hermes has been the default engine since 0.70, and 0.84 made Hermes V1 the default, with a reworked compiler and VM that run JavaScript faster and use less memory. You get it automatically on upgrade.

Practical consequence: there is one JavaScript thread. All of your React rendering, effects, state updates and business logic run on it. If you parse a 5 MB JSON response there, everything that depends on JavaScript waits, including touch handlers written in JavaScript. Remember this; it comes back in the performance section.

Fabric: how a component becomes a native view

Take this component:

export function Greeting() {
  return (
    <View style={{ padding: 16, flexDirection: 'row' }}>
      <Text>Hello from React Native</Text>
    </View>
  );
}

Fabric, the renderer, processes it in three phases:

  1. Render. React runs your component on the JavaScript thread and produces React elements. Fabric turns them into a shadow tree: a tree of immutable C++ nodes, one per host component (View, Text), carrying props. Because the nodes live in C++, JavaScript does not serialise anything to create them; it calls into C++ through JSI.
  2. Commit. Yoga calculates layout for every node: the padding, the row direction, how wide the text is. The new tree is promoted to be the next one to mount. This step can run off the JavaScript thread.
  3. Mount. On the main (UI) thread, Fabric diffs the new tree against what is on screen and applies the minimum set of operations: create a UIView here, update a TextView‘s text there. Those are real platform views, which is why accessibility, text selection and scrolling physics behave natively.

Because the shadow tree is shared C++ and immutable, Fabric can do things the old renderer could not: measure layout synchronously, render concurrently (React 18+ features such as transitions and Suspense work in React Native), and prioritise urgent updates like a text input over a slow list re-render.

TurboModules: calling native code

When JavaScript needs something only the platform can do, it calls a native module. In the New Architecture these are TurboModules. You describe the module once in a typed spec:

// specs/NativeDeviceInfo.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  getBatteryLevel(): number;          // synchronous
  getModelName(): Promise<string>;   // asynchronous
}

export default TurboModuleRegistry.getEnforcing<Spec>('NativeDeviceInfo');
{
  "codegenConfig": {
    "name": "AppSpecs",
    "type": "modules",
    "jsSrcsDir": "specs",
    "android": { "javaPackageName": "com.example.specs" }
  }
}

Codegen reads the spec at build time and generates the C++ glue plus the Objective-C and Java/Kotlin interfaces you implement. Three things follow. Types are checked at the boundary instead of failing at runtime with a mysterious undefined. Methods can be synchronous when that makes sense (reading a cached value), which the bridge never allowed. And modules load lazily, on first use, instead of all at startup, which helps launch time in apps with dozens of native dependencies.

Codegen is also why upgrades sometimes break libraries. In the 0.89 release candidate, libraries that ship pre-generated JNI code must regenerate it, because codegen builds now treat warnings as errors. Our upgrade helper guide walks through handling that kind of break.

Events: how a tap gets back to your code

The platform delivers a touch to the native view. React Native’s native side turns it into an event, figures out which component it targets, and dispatches it to JavaScript, where your onPress runs on the JavaScript thread. Since 0.88, events are dispatched with web-style EventTarget semantics by default (capture and bubble phases you know from the DOM), another step in React Native’s slow convergence with web APIs. structuredClone joining the globals in 0.89 is the same story.

Gestures that must track the finger every frame are the exception. Libraries like React Native Gesture Handler and Reanimated run that logic on the UI thread (Reanimated calls these functions worklets), so a busy JavaScript thread does not make a swipe lag. Since 0.85, React Native core also has a shared animation backend, built with Software Mansion, that both Animated and Reanimated use.

Metro and the build: how it all gets into the app

When you run the app in development, Metro bundles your JavaScript and serves it to the app over the network, which is what makes Fast Refresh possible. For a release build, Metro produces a single bundle, Hermes compiles it to bytecode, and the bytecode is packaged inside the .ipa or .apk next to the native code. Since 0.84, iOS uses precompiled React Native core binaries by default, so a clean build no longer compiles React Native from source.

Expo sits on top of all this rather than replacing it: the same React Native runtime, plus a module system, config plugins that write native configuration for you, and cloud builds. If that distinction is fuzzy, What is Expo covers it.

What this model tells you about performance

Once you see the threads, most performance advice stops being folklore:

  • Janky taps and frozen buttons usually mean the JavaScript thread is busy. Profile it (React Native DevTools has a profiler), move heavy work off it, and memoise expensive renders.
  • Dropped frames in animations mean animation logic is running in JavaScript. Use the native driver or Reanimated so it runs on the UI thread.
  • Slow long lists come from rendering too many items. Virtualise with FlatList or FlashList and keep item components cheap.
  • Heavy drawing (charts, image filters) belongs in a GPU renderer such as Skia, covered in our React Native Skia guide.
  • Slow startup is mostly bundle size and eager work. Hermes bytecode and lazy TurboModules help; importing every screen at the top of App.tsx does not.

I have watched teams spend a week rewriting a screen in native code to fix a stutter that turned out to be a JSON.parse on the JavaScript thread. Measure which thread is busy before you change anything.

Is React Native really native?

The views are native, the navigation stacks can be native (react-native-screens wraps the platform’s own containers), and the gestures can run natively. Your logic runs in JavaScript. That hybrid is the trade-off: you give up some control over the last few milliseconds and some platform features arrive late, and in return you get one codebase and fast iteration. Whether that is the right trade for your app is a separate question, and one we tried to answer honestly in Is React Native dead in 2026?

FAQ

Does React Native use a WebView?

No. React Native renders real platform views. A WebView is only involved if you add one yourself with react-native-webview.

Is React Native compiled to native code?

Not your JavaScript. It is compiled to Hermes bytecode and run by the Hermes engine inside the app. The framework itself, the renderer and native modules are compiled native code (C++, Objective-C, Swift, Java, Kotlin).

Does React Native still use the bridge?

No. Since 0.82 the New Architecture (JSI, Fabric, TurboModules) is the only architecture, and the remaining bridge-era flags are being removed in 0.89. An interop layer lets older libraries keep working.

What is JSI in React Native?

JSI (JavaScript Interface) is a C++ API that lets JavaScript call native functions and hold references to native objects directly, without serialising messages. Fabric and TurboModules are built on it.

How many threads does a React Native app use?

The main ones are the JavaScript thread (React and your logic) and the main UI thread (native views and mounting), with layout able to run in the background. Libraries such as Reanimated add work on the UI thread, and native modules can use their own threads.

Leave a Reply 0

Your email address will not be published. Required fields are marked *