I was leading the architecture for a clinical AI platform at Synapsis Medical Technologies, where we were integrating real-time health data from wearables via Bluetooth Low Energy (BLE). We were hitting a wall with the legacy React Native bridge. High-frequency data packets—heart rate, SpO2, and accelerometer streams—were saturating the bridge, causing noticeable UI stutters and delaying the execution of our RAG-based clinical alerts.
To solve this, we migrated our native modules to the New Architecture (TurboModules) in React Native 0.7x. On paper, it was the perfect solution: synchronous execution, type safety, and no bridge congestion. Instead, our CI/CD pipeline, which I had previously optimized to a 4-hour release cycle, bloated by 25 minutes per build. We saw cryptic C++ compilation errors that stalled our 21-person engineering team for two days. The "type safety" we were promised required a rigid boilerplate that made simple feature additions a multi-file choreography across TypeScript, C++, and Java.
If you are moving to TurboModules because the documentation says it is "faster," you are only hearing half the story. The performance gains are real, but the tax on your developer experience and build infrastructure is significant.
Why the bottleneck moves from the Bridge to the Build
In the legacy architecture, the bridge was a JSON-based message bus. It was slow because every call had to be serialized, passed across the boundary, and deserialized. TurboModules eliminate this by using JSI (JavaScript Interface), allowing JavaScript to hold a direct reference to C++ host objects.
However, JSI requires C++ glue code to bind the JavaScript calls to the native platform code (Objective-C or Java). React Native uses a tool called codegen to generate this glue code.
The failure mode nobody warns you about is the Codegen-Build Loop. In the old way, you changed a Java file and re-ran the app. Now, if you change a single property in your TypeScript interface, codegen must re-run, generating new C++ headers, which then triggers a full recompilation of the native bridge layer. On a standard M2 MacBook Pro, this adds a non-trivial overhead to every native change. If your CI is running on resource-constrained runners, this is where your 4-hour release cycle starts to creep toward 5 hours.
The migration: A step-by-step hardening
Do not follow the "Hello World" tutorials that suggest moving your entire library at once. In my experience shipping 18+ production apps, the only way to survive this without breaking the build for the rest of your team is a staggered implementation.
1. Define the Spec strictly
Create a Native[ModuleName].ts file. The naming convention is not optional; codegen looks for the Native prefix.
// NativeHealthScanner.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
// Use exact types. 'any' will fail codegen.
readonly getSensorData: (sensorId: string) => Promise<string>;
readonly startScan: (frequency: number) => void;
}
export default TurboModuleRegistry.getEnforcing<Spec>('HealthScanner');
Why: codegen is a parser, not a compiler. If you use complex TypeScript unions or external type imports inside this file, the parser will crash with an unhelpful "Task :app:generateCodegenArtifactsFromSchema Failed" error. Keep this file isolated.
2. Trigger Codegen manually to verify
Before you try to compile the whole app, run the script to generate the specs.
# For iOS
cd ios && bundle exec pod install
Confirm it worked: Look into ios/build/generated/ios. You should see HealthScannerSpec.h and HealthScannerSpec-generated.mm. If these files aren't there, your package.json configuration for codegenConfig is likely pointing to the wrong directory.
3. Implement the C++ boilerplate (iOS)
This is where the "cost" becomes visible. You cannot just write Objective-C anymore. You must provide a bridge implementation that conforms to the generated C++ protocol.
// HealthScanner.mm
#import "HealthScanner.h"
@implementation HealthScanner
RCT_EXPORT_MODULE()
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeHealthScannerSpecJSI>(params);
}
// Your actual logic
- (void)startScan:(double)frequency {
// Implementation
}
@end
The Trade-off: You are now maintaining a C++ shared pointer inside an Objective-C++ file. If your team consists purely of React developers, you have just increased the bus factor of your native infrastructure.
The hidden costs of the New Architecture
Having scaled a team from 0 to 21, I’ve seen how these architectural choices impact velocity.
- Binary Size: TurboModules require the inclusion of the JSI and often the Hermes engine (though technically separate, they are designed to work together). In my experience, migrating a medium-sized app to full TurboModules and Fabric (the new renderer) can increase the initial APK size by 3MB to 5MB due to the C++ runtime requirements.
- Compilation Time: As mentioned, our build times increased. Specifically, the
ninjabuild process for C++ is CPU-intensive. If your developers are on 8GB RAM machines, they will feel the swap pressure during every native rebuild. - The "Sync" Trap: TurboModules allow synchronous calls to native. This is tempting for getting constants or simple state. However, if your native method takes more than 5ms (e.g., a quick disk read), you will drop frames on the UI thread because you’ve blocked the JS thread which is now coupled to the native execution.
At your level
Starting out
Do not start your project with TurboModules unless you have a specific performance requirement (like high-frequency data). Stick to the legacy bridge; it is better documented and has more StackOverflow coverage. Focus on learning how to pass data across the boundary before trying to optimize the boundary itself.
Working engineer
When you encounter a "Bridge Busy" warning in your logs, that is your signal to migrate that specific module only. You can run TurboModules and legacy modules side-by-side. Move your heaviest data-producers first—camera buffers, sensor streams, or large file processing.
Senior or staff
You own the build pipeline. Before enabling TurboModules, benchmark your CI. You may need to upgrade your GitHub Actions runners to macos-13-xlarge to offset the C++ compilation time. Establish a strict linting rule for Native[Module].ts files to prevent developers from importing complex types that break the codegen parser.
Lead or director
Understand that moving to the New Architecture is a hiring decision. You are moving away from "JavaScript developers who do a bit of mobile" toward "Systems engineers." Your team will need to be comfortable debugging C++ stack traces and understanding memory management in a hybrid environment.
In the interview
The Question: "Why would you choose TurboModules over the standard React Native bridge?"
The Weak Answer: "Because it's faster and it's the new way React Native works." This is weak because it ignores the implementation cost and assumes "new" equals "better."
The Strong Answer: A strong answer focuses on the JSI (JavaScript Interface). You should explain that TurboModules allow for synchronous execution and eliminate the JSON serialization overhead. You must mention the trade-off: the increased complexity of the codegen process and the requirement for C++ glue code.
The Senior-level Follow-up: "How does the migration affect your CI/CD and developer velocity?"
A candidate who has actually done this will talk about the increase in build times and the brittleness of the codegen parser. They will mention that while runtime performance improves, the feedback loop for native development slows down due to the re-compilation of generated C++ headers. They might also discuss the memory management implications of holding long-lived C++ objects via JSI versus the fire-and-forget nature of the legacy bridge.
Amit Chakraborty is a founding engineer and senior architect — React Native, AI/RAG systems and production architecture. Portfolio: www.amitchakraborty.dev · LinkedIn · GitHub. Open to senior and founding engineering roles, remote worldwide.
Top comments (0)