This content originally appeared on DEV Community and was authored by Nader Alfakesh
Understanding Hermes, Fabric, and the New Architecture in React Native
If you’ve been building React Native apps for a while, chances are you’ve heard terms like Hermes, TurboModules, and Fabric floating around. When I recently upgraded from an older React Native project to v0.80.2, I realized these weren’t just buzzwords—they’re a fundamental shift in how React Native works under the hood.
This guide will demystify these technologies and show you why they matter for your next React Native project.
Table of Contents
- The Evolution of React Native
- Hermes: The JavaScript Engine Built for Mobile
- The New Architecture: Beyond the Bridge
- TurboModules: Native Modules, Supercharged
- Fabric: Modern UI for Modern React
- Migration Guide
- Performance Comparisons
- Conclusion
The Evolution of React Native
Before diving into the new technologies, let’s understand the journey:
📅 Timeline
2015: React Native Launch → JavaScript Bridge Architecture
2017: Hermes Development Begins → Focus on Mobile Performance
2018: New Architecture Announced → JSI Introduction
2022: New Architecture Stable → Fabric & TurboModules
2024: Default in New Projects → Mature Ecosystem
Hermes: The JavaScript Engine Built for Mobile
React Native originally relied on JavaScriptCore (JSC), but Hermes was introduced as a mobile-first JS engine optimized specifically for React Native apps.
Key Benefits:
Faster Startup Time
// Traditional JSC Flow
Source Code → Parse → Compile → Execute
// Hermes Flow
Source Code → Precompile to Bytecode → Execute
Lower Memory Usage
Perfect for low-end Android devices. Here’s a real-world comparison:
Memory Usage Comparison (50MB App):
JSC: ~185MB RAM
Hermes: ~136MB RAM (-26%)
Smaller Bundle Size
Bundle Size Impact:
Before Hermes: 41MB APK
After Hermes: 29MB APK (-29%)
Enabling Hermes in Your Project
Most new React Native projects now enable Hermes by default. Here’s how to check:
Android (android/app/build.gradle):
android {
...
packagingOptions {
pickFirst '**/libc++_shared.so'
pickFirst '**/libjsc.so'
}
}
// Hermes is enabled by this flag
hermesEnabled = true
iOS (ios/Podfile):
use_react_native!(
:path => config[:reactNativePath],
# Hermes is now enabled by default
:hermes_enabled => true,
:fabric_enabled => flags[:fabric_enabled],
)
The New Architecture: Beyond the Bridge
The classic React Native model relied on a Bridge for JS-to-Native communication:
Old Bridge Architecture:
┌─────────────┐ JSON ┌──────────────┐
│ JavaScript │ ←----------→ │ Native │
│ Thread │ Serialize │ Thread │
└─────────────┘ └──────────────┘
↓ ↓
[Async Queue] [Processing]
↓ ↓
[Batching] [Response]
New JSI Architecture:
┌─────────────┐ ┌──────────────┐
│ JavaScript │ ←----------→ │ Native │
│ Thread │ Direct C++ │ Thread │
└─────────────┘ Interface └──────────────┘
↓ ↓
[Immediate] [Immediate]
Performance Impact:
// Old Bridge (Async)
NativeModules.Camera.takePicture((result) => {
// Wait for bridge...
console.log(result); // ~16-32ms delay
});
// New JSI (Sync when needed)
const result = Camera.takePictureSync();
console.log(result); // <1ms delay
TurboModules: Native Modules, Supercharged
TurboModules revolutionize how we interact with native code:
Key Features:
- Lazy Loading: Modules load only when needed
- Type Safety: Auto-generated TypeScript bindings
- Synchronous Calls: When performance matters
- Direct JSI Access: No serialization overhead
Creating a TurboModule:
1. Define the Native Module Interface:
// specs/NativeDeviceInfo.ts
import type {TurboModule} from 'react-native';
import {TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
getBatteryLevel(): number;
getDeviceId(): string;
isTablet(): boolean;
}
export default TurboModuleRegistry.getEnforcing<Spec>('DeviceInfo');
2. Native Implementation (iOS):
// RNDeviceInfo.mm
#import "RNDeviceInfo.h"
@implementation RNDeviceInfo
RCT_EXPORT_MODULE(DeviceInfo)
- (NSNumber *)getBatteryLevel {
UIDevice *device = [UIDevice currentDevice];
device.batteryMonitoringEnabled = YES;
return @(device.batteryLevel);
}
- (NSString *)getDeviceId {
return [[[UIDevice currentDevice] identifierForVendor] UUIDString];
}
- (NSNumber *)isTablet {
return @(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
}
@end
3. Usage in JavaScript:
import DeviceInfo from './specs/NativeDeviceInfo';
// Direct, synchronous calls!
const batteryLevel = DeviceInfo.getBatteryLevel();
const deviceId = DeviceInfo.getDeviceId();
const isTablet = DeviceInfo.isTablet();
console.log(`Battery: ${batteryLevel * 100}%`);
console.log(`Device ID: ${deviceId}`);
console.log(`Is Tablet: ${isTablet}`);
Fabric: Modern UI for Modern React
Fabric is React Native’s new rendering system, built from the ground up to support modern React features:
Architecture Comparison:
Old Renderer:
React Components
↓
Shadow Thread (Layout)
↓
Bridge (JSON)
↓
Main Thread (UI)
Fabric Renderer:
React Components
↓
Fabric C++ Core
↙ ↘
iOS UI Android UI
(Sync) (Sync)
Key Improvements:
1. Concurrent Rendering Support
// Now works seamlessly with React 18 features
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<ExpensiveComponent />
</Suspense>
);
}
2. Priority-based Rendering
// High priority updates (user input)
<TextInput onChangeText={setText} />
// Low priority updates (background data)
<LargeList data={backgroundData} />
3. Better Animations
// Fabric enables smoother 60fps animations
const animatedValue = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{
translateX: withSpring(animatedValue.value * 100)
}]
}));
Performance Comparisons
Real-world Metrics:
┌─────────────────┬──────────┬──────────┬─────────┐
│ Metric │ Old Arch │ New Arch │ Change │
├─────────────────┼──────────┼──────────┼─────────┤
│ App Start Time │ 3.2s │ 1.8s │ -44% │
│ List Scroll FPS │ 47 fps │ 59 fps │ +25% │
│ Memory Usage │ 185 MB │ 136 MB │ -26% │
│ Bundle Size │ 41 MB │ 29 MB │ -29% │
└─────────────────┴──────────┴──────────┴─────────┘
Benchmark Code:
// Measure startup performance
const startTime = Date.now();
AppRegistry.registerComponent(appName, () => App);
// Log after first render
const FirstRenderTracker = () => {
useEffect(() => {
console.log(`First render: ${Date.now() - startTime}ms`);
}, []);
return null;
};
Best Practices
1. Gradual Migration
// Start with non-critical modules
const MyModule = Platform.select({
ios: NativeModules.MyModuleOld,
android: TurboModuleRegistry.get<Spec>('MyModule'),
});
2. Type Safety First
// Always define TypeScript specs
interface Spec extends TurboModule {
readonly constantsToExport: {
readonly apiUrl: string;
readonly version: string;
};
methodWithCallback(callback: (result: string) => void): void;
}
3. Performance Testing
// Use React DevTools Profiler
import {Profiler} from 'react';
<Profiler id="MyComponent" onRender={(id, phase, duration) => {
console.log(`${id} (${phase}) took ${duration}ms`);
}}>
<MyComponent />
</Profiler>
Conclusion
The new React Native architecture isn’t just an incremental update—it’s a fundamental reimagining of how JavaScript and native code communicate. With:
- Hermes providing faster startup and lower memory usage
- JSI eliminating the bridge bottleneck
- TurboModules offering type-safe, lazy-loaded native modules
- Fabric bringing modern React features to mobile
…your React Native apps can now rival truly native performance while maintaining the developer experience we love.
This content originally appeared on DEV Community and was authored by Nader Alfakesh