React Native's New Architecture represents the most significant evolution of the framework since its inception. With Fabric replacing the legacy renderer and TurboModules enabling synchronous native module access, applications can now achieve near-native performance. In 2026, the New Architecture is production-ready and increasingly required by popular libraries. This comprehensive guide covers everything you need to migrate your apps and leverage these powerful new capabilities.
Understanding the New Architecture
The New Architecture consists of three main pillars that work together to dramatically improve performance and enable new capabilities that were previously impossible with the legacy bridge-based architecture.
- Fabric: A new rendering system that enables synchronous, thread-safe communication between JavaScript and native UI
- TurboModules: A new native modules system with lazy loading and synchronous access capabilities
- JSI (JavaScript Interface): A lightweight C++ interface allowing direct communication between JavaScript and native code without serialization
- Codegen: Automatic type-safe code generation from TypeScript/Flow specifications
Performance Improvements
The bridge-based architecture required all communication between JavaScript and native code to be serialized to JSON and passed asynchronously. This created latency and made certain patterns impossible. JSI eliminates this bottleneck, enabling direct memory sharing and synchronous calls when needed.
Migration Prerequisites
# Check your React Native version (requires 0.71+ for stable New Architecture)
npx react-native --version
# Update to latest React Native
npx react-native upgrade
# Or create a new project with New Architecture enabled
npx react-native init MyApp --version 0.73.0
# Verify New Architecture compatibility of your dependencies
npx react-native-new-arch-checkEnabling the New Architecture
The New Architecture can be enabled separately for Android and iOS. It's recommended to enable and test one platform at a time during migration.
Android Configuration
// android/gradle.properties
# Enable New Architecture
newArchEnabled=true
# Enable Hermes (required for New Architecture)
hermesEnabled=true
// android/app/build.gradle - Additional configuration
android {
defaultConfig {
// Enable BuildConfig for native modules
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", "${isNewArchitectureEnabled()}"
}
}
def isNewArchitectureEnabled() {
return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
}iOS Configuration
# ios/Podfile
# Enable New Architecture flags
ENV['RCT_NEW_ARCH_ENABLED'] = '1'
platform :ios, min_ios_version_supported
prepare_react_native_project!
linkage = ENV['USE_FRAMEWORKS']
if linkage != nil
Pod::UI.puts "Configuring Pod with #{linkage}ing framework linkage."
use_frameworks! :linkage => linkage.to_sym
end
target 'MyApp' do
config = use_native_modules!
use_react_native!(
:path => config[:reactNativePath],
# Enable Fabric and TurboModules
:fabric_enabled => true,
:new_arch_enabled => true,
# Hermes is required
:hermes_enabled => true,
# App path for codegen
:app_path => "#{Pod::Config.instance.installation_root}/.."
)
post_install do |installer|
react_native_post_install(
installer,
config[:reactNativePath],
:mac_catalyst_enabled => false
)
end
end# Reinstall pods with New Architecture
cd ios && pod install --clean-install && cd ..
# Clean and rebuild
npx react-native start --reset-cache
npx react-native run-iosCreating TurboModules
TurboModules replace the legacy native modules system. They offer lazy loading, synchronous access, and type safety through Codegen. Here's how to create a TurboModule with full TypeScript support.
// src/specs/NativeDeviceInfo.ts - TurboModule Specification
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
// Synchronous method - executes on JS thread
getDeviceId(): string;
// Async method - executes on native thread
getBatteryLevel(): Promise<number>;
// Method with complex types
getDeviceInfo(): Promise<{
brand: string;
model: string;
systemVersion: string;
isTablet: boolean;
}>;
// Method with parameters
setUserPreference(key: string, value: string): Promise<boolean>;
// Event emitter support
addListener(eventName: string): void;
removeListeners(count: number): void;
}
export default TurboModuleRegistry.getEnforcing<Spec>('DeviceInfo');// android/app/src/main/java/com/myapp/DeviceInfoModule.kt
package com.myapp
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.WritableNativeMap
import com.facebook.react.module.annotations.ReactModule
import android.os.Build
import android.os.BatteryManager
import android.content.Context
@ReactModule(name = DeviceInfoModule.NAME)
class DeviceInfoModule(reactContext: ReactApplicationContext) :
NativeDeviceInfoSpec(reactContext) {
companion object {
const val NAME = "DeviceInfo"
}
override fun getName() = NAME
// Synchronous method
override fun getDeviceId(): String {
return Build.ID
}
// Async method
override fun getBatteryLevel(promise: Promise) {
try {
val batteryManager = reactApplicationContext
.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
val level = batteryManager
.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
promise.resolve(level.toDouble())
} catch (e: Exception) {
promise.reject("ERROR", e.message)
}
}
override fun getDeviceInfo(promise: Promise) {
val info = WritableNativeMap().apply {
putString("brand", Build.BRAND)
putString("model", Build.MODEL)
putString("systemVersion", Build.VERSION.RELEASE)
putBoolean("isTablet", isTablet())
}
promise.resolve(info)
}
override fun setUserPreference(key: String, value: String, promise: Promise) {
try {
val prefs = reactApplicationContext
.getSharedPreferences("user_prefs", Context.MODE_PRIVATE)
prefs.edit().putString(key, value).apply()
promise.resolve(true)
} catch (e: Exception) {
promise.reject("ERROR", e.message)
}
}
private fun isTablet(): Boolean {
val metrics = reactApplicationContext.resources.displayMetrics
val widthInches = metrics.widthPixels / metrics.xdpi
val heightInches = metrics.heightPixels / metrics.ydpi
val diagonalInches = Math.sqrt(
(widthInches * widthInches + heightInches * heightInches).toDouble()
)
return diagonalInches >= 7.0
}
}// Usage in React Native app
import NativeDeviceInfo from './specs/NativeDeviceInfo';
import { useEffect, useState } from 'react';
import { Text, View } from 'react-native';
function DeviceInfoDisplay() {
const [deviceInfo, setDeviceInfo] = useState<{
brand: string;
model: string;
systemVersion: string;
isTablet: boolean;
} | null>(null);
const [batteryLevel, setBatteryLevel] = useState<number>(0);
// Synchronous call - safe to use in render
const deviceId = NativeDeviceInfo.getDeviceId();
useEffect(() => {
async function loadDeviceInfo() {
const info = await NativeDeviceInfo.getDeviceInfo();
setDeviceInfo(info);
const battery = await NativeDeviceInfo.getBatteryLevel();
setBatteryLevel(battery);
}
loadDeviceInfo();
}, []);
return (
<View style={{ padding: 20 }}>
<Text>Device ID: {deviceId}</Text>
{deviceInfo && (
<>
<Text>Brand: {deviceInfo.brand}</Text>
<Text>Model: {deviceInfo.model}</Text>
<Text>OS Version: {deviceInfo.systemVersion}</Text>
<Text>Is Tablet: {deviceInfo.isTablet ? 'Yes' : 'No'}</Text>
</>
)}
<Text>Battery: {batteryLevel}%</Text>
</View>
);
}Creating Fabric Components
Fabric components replace legacy native UI components with improved performance and capabilities. The key difference is that Fabric components use direct manipulation rather than the bridge, enabling smoother animations and interactions.
// src/specs/CustomButtonNativeComponent.ts
import type { ViewProps, ColorValue } from 'react-native';
import type {
DirectEventHandler,
Double,
} from 'react-native/Libraries/Types/CodegenTypes';
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
type OnPressEvent = Readonly<{
timestamp: Double;
}>;
interface NativeProps extends ViewProps {
// String props
title: string;
// Color props (use ColorValue type)
backgroundColor?: ColorValue;
textColor?: ColorValue;
// Numeric props
cornerRadius?: Double;
// Boolean props
disabled?: boolean;
loading?: boolean;
// Event handlers
onPress?: DirectEventHandler<OnPressEvent>;
}
export default codegenNativeComponent<NativeProps>('CustomButton', {
interfaceOnly: true,
excludedPlatforms: ['web'],
});// src/components/CustomButton.tsx - Wrapper component
import React from 'react';
import { StyleSheet, ActivityIndicator, View } from 'react-native';
import NativeCustomButton from '../specs/CustomButtonNativeComponent';
interface CustomButtonProps {
title: string;
onPress?: () => void;
backgroundColor?: string;
textColor?: string;
cornerRadius?: number;
disabled?: boolean;
loading?: boolean;
style?: object;
}
export function CustomButton({
title,
onPress,
backgroundColor = '#007AFF',
textColor = '#FFFFFF',
cornerRadius = 8,
disabled = false,
loading = false,
style,
}: CustomButtonProps) {
const handlePress = React.useCallback(
(event: { nativeEvent: { timestamp: number } }) => {
if (!disabled && !loading && onPress) {
onPress();
}
},
[disabled, loading, onPress]
);
return (
<View style={[styles.container, style]}>
<NativeCustomButton
title={title}
backgroundColor={backgroundColor}
textColor={textColor}
cornerRadius={cornerRadius}
disabled={disabled || loading}
loading={loading}
onPress={handlePress}
style={styles.button}
/>
{loading && (
<ActivityIndicator
style={styles.loader}
color={textColor}
/>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
position: 'relative',
},
button: {
minHeight: 44,
justifyContent: 'center',
alignItems: 'center',
},
loader: {
position: 'absolute',
right: 16,
top: '50%',
marginTop: -10,
},
});Performance Optimization Tips
The New Architecture provides the foundation for better performance, but you still need to follow best practices to fully leverage its capabilities.
// Performance optimization patterns for New Architecture
import React, { useMemo, useCallback, memo } from 'react';
import { FlatList, StyleSheet, Pressable, Text, View } from 'react-native';
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
} from 'react-native-reanimated';
// 1. Use Reanimated for smooth animations (runs on UI thread)
function AnimatedCard({ item }: { item: ItemType }) {
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
const handlePressIn = useCallback(() => {
scale.value = withSpring(0.95);
}, []);
const handlePressOut = useCallback(() => {
scale.value = withSpring(1);
}, []);
return (
<Pressable onPressIn={handlePressIn} onPressOut={handlePressOut}>
<Animated.View style={[styles.card, animatedStyle]}>
<Text>{item.title}</Text>
</Animated.View>
</Pressable>
);
}
// 2. Memoize list items properly
const MemoizedCard = memo(AnimatedCard, (prev, next) => {
return prev.item.id === next.item.id;
});
// 3. Optimize FlatList with getItemLayout for fixed-height items
function OptimizedList({ data }: { data: ItemType[] }) {
const ITEM_HEIGHT = 80;
const SEPARATOR_HEIGHT = 1;
const getItemLayout = useCallback(
(_: any, index: number) => ({
length: ITEM_HEIGHT,
offset: (ITEM_HEIGHT + SEPARATOR_HEIGHT) * index,
index,
}),
[]
);
const renderItem = useCallback(
({ item }: { item: ItemType }) => <MemoizedCard item={item} />,
[]
);
const keyExtractor = useCallback((item: ItemType) => item.id, []);
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={keyExtractor}
getItemLayout={getItemLayout}
// Performance props
removeClippedSubviews={true}
maxToRenderPerBatch={10}
windowSize={5}
initialNumToRender={10}
// Prevent re-renders
extraData={null}
/>
);
}
// 4. Use synchronous TurboModule calls wisely
import NativePerformance from './specs/NativePerformance';
function PerformanceCriticalComponent() {
// Synchronous call - use only for quick operations
const timestamp = NativePerformance.getHighResTimestamp();
// For expensive operations, still use async
useEffect(() => {
NativePerformance.logPerformanceMetric('component_render', timestamp);
}, [timestamp]);
}Common Migration Issues
Common Migration Pitfalls
Incompatible Libraries: Check library compatibility before migrating. Many popular libraries now support New Architecture, but some legacy ones don't.
Codegen Errors: Ensure your spec files have proper TypeScript types. Codegen is strict about type definitions.
iOS Build Failures: Clean DerivedData and Pods cache when switching architecture modes.
Android Build Failures: Ensure NDK is properly configured and Hermes is enabled.
Runtime Crashes: TurboModules with incorrect specs will crash at runtime. Test thoroughly.
# Troubleshooting commands
# Clean iOS build
cd ios && rm -rf Pods Podfile.lock build
rm -rf ~/Library/Developer/Xcode/DerivedData
pod install --clean-install
cd ..
# Clean Android build
cd android && ./gradlew clean
rm -rf .gradle app/build
cd ..
# Reset Metro cache
npx react-native start --reset-cache
# Verify Codegen output
cd android && ./gradlew generateCodegenArtifactsFromSchema
cd ../ios && pod installFrequently Asked Questions
Frequently Asked Questions
What is the React Native New Architecture?
The New Architecture consists of Fabric (new rendering system) and TurboModules (new native modules system). It enables synchronous native calls, better performance through JSI, and concurrent rendering support.
Should I migrate my app to the New Architecture?
Yes, if you're on React Native 0.71+. The New Architecture is now the default and provides significant performance improvements. Most major libraries now support it. Start with a feature branch and test thoroughly.
How much faster is the New Architecture?
Apps typically see 20-40% improvement in startup time and smoother animations due to reduced bridge overhead. Synchronous native calls and concurrent rendering eliminate many performance bottlenecks.
What is Codegen and why is it required?
Codegen generates type-safe native interfaces from your JavaScript/TypeScript specs. It ensures type safety between JS and native code, catches errors at build time, and enables the JSI performance benefits.
My third-party library doesn't support New Architecture. What should I do?
Check the library's GitHub for New Architecture support or PRs. Many libraries have been updated. If not, you can use the interop layer temporarily, or consider alternative libraries that support it.
Conclusion
The React Native New Architecture is a significant leap forward for cross-platform mobile development. While migration requires careful planning and testing, the performance improvements and new capabilities make it worthwhile. Start by enabling the New Architecture on a feature branch, migrate your native modules to TurboModules, and update your UI components to use Fabric where beneficial.
As the ecosystem continues to mature, more libraries will support the New Architecture natively. By migrating now, you're positioning your app for the future of React Native development.
Need help migrating your React Native app to the New Architecture? Contact Jishu Labs for expert mobile development services. Our team has successfully migrated numerous production apps and can guide your transition.
About Emily Rodriguez
Emily Rodriguez is a Senior Mobile Engineer at Jishu Labs with expertise in React Native and native mobile development. She has built mobile applications serving millions of users across iOS and Android.