Bridge is an architectural component of React Native that provides asynchronous communication between the JavaScript thread and the native environment of iOS and Android. It transmits serialized JSON messages through a queue, allowing native APIs to be called from JS code. According to Meta, 2024, Bridge remains the foundation of existing applications, although it lags in performance compared to the new architecture on JSI.
Key Takeaways
Bridge is a key architectural element of React Native that provides two-way asynchronous communication between the JavaScript thread, where the application business logic runs, and the native iOS and Android threads. Since the release of React Native in 2015, Bridge has remained the only way for JS code to interact with platform APIs — camera, geolocation, file system, notifications, and other native capabilities.
The Bridge architecture is based on the message queue principle. When JavaScript code calls a native method, the request is serialized into a JSON string, placed in a queue, and asynchronously sent to the native side. The native code processes the request, performs the corresponding operation, and sends the result back through the same queue to the JS thread. According to Meta’s report at React Conf 2021, up to 10,000 messages per second pass through Bridge in an average application.
The main threads involved in Bridge operation: JavaScript Thread (JS code execution), Native Thread (native operations execution), and Shadow Thread (layout calculation using Yoga). Each thread works independently, ensuring UI responsiveness — native animations are not blocked by JS computations.
Bridge uses three key mechanisms for communication: MessageQueue, JSON serialization, and message batching. MessageQueue is an internal React Native component that manages the call queue between JS and the native side. Each native method call is placed in a queue, serialized, and sent in batches for performance optimization.
MessageQueue operates on a batching principle: native method calls accumulate and are sent as a single group (batch) every 5–15 milliseconds. This reduces serialization overhead, as multiple calls are packed into one JSON package. On the native side, messages are deserialized and distributed to the corresponding modules.
Native modules are registered automatically through macros or annotations. iOS uses the RCT_EXPORT_MODULE macro, Android uses the @ReactMethod annotation. React Native scans registered modules at application startup and builds a configuration JSON map of all available methods. This map is passed to the JS environment, and JavaScript learns which methods can be called.
Data follows this path: JavaScript calls NativeModules.CalendarModule.createCalendarEvent(). The method is serialized into a JSON message with the module identifier, method name, and arguments. The message enters the MessageQueue. On the native thread, the message is deserialized and passed to the corresponding module. The execution result is serialized back and sent to the JS thread as a Promise or callback.
// Native module call from JavaScript via Bridge
import { NativeModules } from 'react-native';
const CalendarModule = NativeModules.CalendarModule;
CalendarModule.createCalendarEvent('Test Event', 'Office')
.then(eventId => {
console.log('Created event with id:', eventId);
})
.catch(error => {
console.error('Failed:', error);
});
On the native iOS side, the module looks like an Objective-C class with the RCT_EXPORT_MODULE macro. The method is exported using the RCT_EXPORT_METHOD macro, and React Native automatically registers it in Bridge. Arguments are passed by position and must correspond to supported JSON types: NSString, NSNumber, NSArray, NSDictionary, BOOL.
// iOS Native Module registration in Bridge
@interface CalendarModule () RCT_EXPORT_MODULE()
@end
@implementation CalendarModule
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name
location:(NSString *)location
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSNumber *eventId = createEvent(name, location);
resolve(eventId);
}
@end
Bridge has a number of fundamental performance limitations. The main one is mandatory asynchronicity and serialization. Each native method call converts data into a JSON string, which adds latency and consumes memory. For operations with large amounts of data, such as image processing or video work, this becomes a bottleneck.
JSON serialization and deserialization consume CPU time and memory. Each message must be converted to a string on the JS side, transmitted through the bridge, and parsed on the native side. According to Callstack tests (2022), serializing an array of 10,000 numbers through Bridge takes about 30–50 milliseconds, which is unacceptable for high-frequency calls.
Bridge is not optimized for transferring large binary data. Photos, audio files, and video streams require alternative approaches — for example, writing a file to disk and passing the path as a string. This creates additional overhead for file system read and write operations.
Recognition of these limitations led the Meta team to develop a new React Native architecture, where Bridge is replaced by JSI (JavaScript Interface) and Turbo Module. JSI allows calling native methods directly, without serialization, eliminating the main drawback of Bridge.
The comparison of Bridge and Turbo Module shows fundamental differences in architectural approaches. Bridge uses an asynchronous message queue with JSON serialization, while Turbo Module works through JSI — a direct interface between JavaScript and C++ that allows synchronous calling of native methods without data conversion.
| Characteristic | Bridge | Turbo Module |
|---|---|---|
| Call Type | Asynchronous | Synchronous and asynchronous |
| Serialization | JSON on every call | JSI objects without copying |
| Performance | Average | High |
| Typing | Dynamic | Static (Codegen) |
| Loading | All modules at startup | Lazy (on demand) |
The choice between Bridge and Turbo Module depends on the React Native version. For projects on React Native 0.72 and older, Bridge remains the main mechanism. Starting with React Native 0.73, Metro and the new architecture are supported in parallel, allowing gradual migration. A full transition to Turbo Module requires updating to React Native 0.76+ and enabling the new architecture in the configuration.
Let’s walk through the complete cycle of creating and using a Native Module through Bridge using a calendar module as an example. The module will create an event and return its identifier. This example covers setup for both platforms — iOS and Android.
On Android, a Native Module is created as a Java class extending ReactContextBaseJavaModule. The @ReactMethod annotation exports the method to Bridge. For Promise, the Promise interface from com.facebook.react.bridge is used.
public class CalendarModule extends ReactContextBaseJavaModule {
@Override
public String getName() {
return "CalendarModule";
}
@ReactMethod
public void createCalendarEvent(
String name,
String location,
Promise promise) {
try {
Integer eventId = createCalendarEventNative(name, location);
promise.resolve(eventId);
} catch (Exception e) {
promise.reject("EVENT_ERROR", e.getMessage());
}
}
}
The module is registered through @ReactModule or manually in the application package. React Native automatically detects and adds it to Bridge. After registration, the module is accessible from JavaScript via NativeModules.
public class CalendarPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
return Arrays.asList(
new CalendarModule(reactContext)
);
}
@Override
public List<ViewManager> createViewManagers(
ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
It is important to note that Bridge requires an application restart when adding new modules, as the configuration map is built once during initialization. This distinguishes it from Turbo Module, which loads lazily and supports hot-reload of modules without restart.
Frequently Asked Questions
Bridge always uses an asynchronous queue and JSON serialization, while direct transfer via JSI works synchronously and without data copying. Bridge creates serialization latency but ensures thread isolation.
No, Bridge only supports asynchronous calls. Synchronous interaction requires the new architecture with JSI and Turbo Module. This is one of the key limitations that has been resolved in React Native 0.76+.
Bridge supports types serializable to JSON: strings, numbers, boolean values, arrays, dictionaries (objects). Binary data such as images must be transferred via the file system or base64 encoding.
For measurement, use React DevTools and the React Native profiler. The Performance tab shows the number of messages in the Bridge queue and latencies. The react-native-bridge-spy package is also available for traffic monitoring.
It is recommended to switch for performance-demanding projects or when creating new applications on React Native 0.76+. For existing projects, migration can be gradual — both architectures work in parallel.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also