React Native Rendering, Part 3: JSI — Removing the Bridge
Part 2 ended on a question. The legacy architecture joins JavaScript and native with a Bridge — an asynchronous, serialized, batched mail service. But JavaScript and the native UI live on the same device, in the same app, often in the same process. So why mail letters at all?
The answer, of course, is that the Bridge was never a physical necessity. It was a design choice — a way to keep the two worlds safely apart. This article is about the choice that replaced it: JSI, the JavaScript Interface, and why it is the foundation stone of React Native's New Architecture.
What You'll Learn
- What JSI is: a small C++ layer that lets JavaScript and C++ hold references to each other's objects
- How JSI removes the Bridge's serialization tax and enables synchronous calls
- How JSI relates to the rest of the New Architecture: TurboModules, Fabric, Codegen, and Hermes
- Why JSI is a primitive, not a magic performance switch — and its real threading caveats
Prerequisites
- You've met the three threads and the Bridge:
- And why React Native had to build its own pipeline in the first place:
Starting from a Scenario
Suppose you're writing a camera library. Frames arrive from the camera as large buffers — call it 30 MB per frame. At 60 frames per second, that is roughly 1.8 GB of data per second flowing from native to JavaScript.
Now try to push 1.8 GB/s through the Bridge. Remember what the Bridge does: it serializes every message into a portable format, queues it, and parses it on the other side. For small, occasional calls, that overhead is invisible. For a continuous torrent of multi-megabyte buffers, the serialization alone would sink the frame rate.
And in the legacy world there is no way around it: every JS↔native interaction is a message. You cannot hand JavaScript a pointer to the frame buffer, because the Bridge only carries serialized values, not references.
So the question behind JSI is: can we let JavaScript and C++ share objects directly, with no serialization at all?
Core Content
1. What JSI is
JSI stands for JavaScript Interface. It is a small C++ API that sits between the JavaScript engine and the native (C++) world. Its job is to make the two the same world: it lets C++ create and inspect JavaScript values, and — the important direction — it lets JavaScript hold a reference to a C++ object and call methods on it directly.
Think about what the web gives you. In Part 1 we noted one of the browser's three gifts: a host tree JavaScript can manipulate. document.getElementById('x') returns an object, and you call .focus() or read .offsetWidth on it. That object is not a copy of the DOM node — it is the DOM node, reached through a live reference. The serialization problem never arises, because JavaScript and the DOM share an address space.
JSI gives React Native exactly this superpower, but for the native / C++ side:
Web: JavaScript ─── live reference ───► DOM node (C++)
React Native (JSI): JavaScript ─── live reference ───► C++ objectA C++ object can be installed into a JavaScript runtime; to JavaScript it looks like an ordinary object, but reading a property or calling a method routes straight into C++.
Analogy: in Part 2 the Bridge was mail between two offices. JSI is closer to the two offices sharing one open-plan floor: you can walk over and point at the actual object. No letters, no copies.
2. How JSI erases the Bridge's costs
Recall the three costs of the Bridge: no synchronous return values, serialization on every message, and batching latency. JSI attacks all three:
- Synchronous calls. Because JS calls a C++ function directly (through the JSI layer), the call can return a value synchronously. No "drop a message and wait for the reply."
- No serialization. JSI passes references, not copied data. A 30 MB frame buffer is a C++ object; JavaScript gets a handle to it, not a JSON copy. The cost of "passing" it is near zero.
- No batching latency. There is no queue in the middle deciding when to wake the other side. The call happens now.
This is why JSI is described as "removing the Bridge." It does not make the Bridge faster — it makes the Bridge unnecessary.
3. The engine-agnostic trick (and Hermes)
One more property makes JSI special: it is engine-agnostic. It does not assume JavaScriptCore, or V8, or anything else. It defines its own view of a JavaScript runtime — values, objects, functions, the global object — and implementations translate that to whatever engine is actually running.
Concretely, JSI exposes things like:
- a runtime (the JavaScript virtual machine),
- values, objects, arrays, functions, strings as C++-side handles to JavaScript values,
- host functions and host objects: C++ code that appears to JavaScript as a callable function or an ordinary object,
- native state: a way for a JS object to carry a C++ object inside it.
Because JSI hides which engine is underneath, React Native can swap engines. And that is exactly what happened with Hermes, Meta's JavaScript engine built for React Native: JSI is the seam that let React Native adopt it without rewriting everything.
4. What JSI unlocked: the New Architecture
JSI by itself is "just" a way to call across the language boundary. Its significance is what gets built on top of it. Four things:
-
TurboModules — the new native module system. In the legacy world, a native module was reached over the Bridge. With JSI, a native module is a C++-backed object JavaScript can call directly, and it can support synchronous methods. TurboModules are also lazily loaded: a module is not initialized until the first time you use it, which cuts startup time.
-
Fabric — the new renderer. This is the big one for us. With JSI, JavaScript can build and manipulate the renderer's C++ tree directly and synchronously. The Shadow Tree that Part 2's async pipeline had to shuttle around can now be touched in place. We will spend the next articles on this.
-
Codegen — type-safe bindings. You describe your native module's interface in JavaScript/TypeScript; Codegen generates the C++ (and Java / Objective-C) glue that connects it to JSI. If the JS and native sides disagree about a type, you get a build error, not a runtime surprise.
-
Hermes — the engine, as above.
Together: JSI is the foundation; the other four are the house built on it.
5. What JSI is not
It is easy to hear "removing the Bridge" and assume JSI is a magic performance switch. It is not. Two caveats matter:
-
JSI has no threading model of its own. A JSI call runs on the thread that makes it — normally the JS thread. If you call a C++ function synchronously, you block the JS thread for the duration. Moving work to another thread (say, the UI thread) requires other mechanisms on top of JSI, like a runtime scheduler, or the worklet runtimes that power Reanimated. JSI gives you the pipe; it does not decide who speaks.
-
JSI is a low-level primitive. Application developers rarely write JSI directly. You use the abstractions built on it — TurboModules and Fabric — and let Codegen wire them up. Raw JSI is mostly the territory of library authors.
The honest summary: JSI removes the architectural bottleneck of serialization and asynchrony. It does not remove work. You still have to be careful about what you do, and on which thread.
6. The picture going forward
Here is the mental model to carry into the rest of the series:
Part 2 (legacy): JS ──serialize──► Bridge ──serialize──► native
Part 3+ (new): JS ◄──────────── JSI ─────────────► C++ (shared)The two worlds are now joined directly. That single change makes it possible to do what concurrent React needs — read layout synchronously, render on the UI thread, keep multiple trees in flight. Possible — but not automatic. Turning possibility into a working renderer is the job of Fabric, and that is where we go next.
Hands-On Practice
Basic Exercises
-
Find JSI's fingerprints. In a project on the New Architecture, reach for a TurboModule that exposes a synchronous method (a module's constants are a common one) and notice it can be read without
await— something the async Bridge could not do. -
Compare the shapes. Put an old-style native module call (
NativeModules.Foo.bar().then(...)) next to a TurboModule sync call (Foo.getSomething()). Theawaitdisappearing is the Bridge disappearing. -
Read the generated types. Open a library that ships a TurboModule spec (
Native*.ts) and find its Codegen output. You are looking at the JSI glue that used to be hand-written.
Advanced Reflection
JSI lets a JS thread call C++ synchronously. Given Part 2's three threads, explain why "synchronous" is a double-edged word: what does it buy you, and what does it cost, if you call the wrong thing on the wrong thread?
Summary & Keywords
- JSI (JavaScript Interface) is a small, engine-agnostic C++ layer that lets JavaScript and C++ hold live references to each other's objects and call across the boundary directly.
- It removes the Bridge's three costs: no serialization (references, not copies), synchronous return values, and no batching latency.
- Its engine-agnostic design is what let React Native adopt Hermes.
- It is the foundation of the New Architecture: TurboModules (lazy, sync-capable native modules), Fabric (the new renderer), Codegen (type-safe bindings), and Hermes.
- JSI is a primitive, not a magic switch: it has no threading model of its own, and a synchronous call blocks the calling thread. It removes the architectural bottleneck, not the need for care.
Keywords: JSI, JavaScript Interface, HostObject, TurboModules, Fabric, Codegen, Hermes, synchronous call, serialization, engine-agnostic
Further Reading and Next Article Preview
The pipe is in place. Now we can ask the rendering question again — but with a new capability in hand: JavaScript can reach into a C++ tree and change it in place. The next article meets that tree: the Shadow Tree, React Native's answer to the DOM, and the reason it can stay immutable, thread-safe, and fast all at once.
Next up: React Native Rendering, Part 4: The Shadow Tree — React Native's DOM.
