π 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.
Top comments (0)