Mobile Development18 min read1,832 words

React Native New Architecture: Complete Migration Guide for 2026

Master the React Native New Architecture with Fabric renderer and TurboModules. Learn migration strategies, performance optimizations, and best practices for building high-performance cross-platform mobile applications.

ER

Emily Rodriguez

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

bash
# 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-check

Enabling 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

groovy
// 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

ruby
# 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
bash
# 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-ios

Creating 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.

typescript
// 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');
cpp
// 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
    }
}
typescript
// 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.

typescript
// 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'],
});
typescript
// 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.

typescript
// 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.

bash
# 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 install

Frequently 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.

ER

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.

Related Articles

Mobile Development18 min read

SwiftUI for iOS Development 2026: Building Modern Apple Apps

Master SwiftUI for building modern iOS, macOS, watchOS, and visionOS applications. Learn declarative UI patterns, state management, navigation, and performance optimization techniques.

Riken Patel

December 28, 2025

Ready to Build Your Next Project?

Let's discuss how our expert team can help bring your vision to life.

Top-Rated
Software Development
Company

Ready to Get Started?

Get consistent results. Collaborate in real-time.
Build Intelligent Apps. Work with Jishu Labs.

SCHEDULE MY CALL