Technical

OTA Updates for React Native New Architecture

How to use OTA updates with React Native's New Architecture (Fabric renderer and TurboModules). Complete compatibility guide for modern React Native apps.

S
SwiftPatch Team
Engineering
10 min read

Introduction

React Native's New Architecture is the biggest change to the framework since its creation. With the Fabric renderer, TurboModules, and the JavaScript Interface (JSI), React Native apps are now faster, more responsive, and closer to native performance than ever before.

But this architectural shift has created a problem: most OTA update solutions don't work with the New Architecture. CodePush, the most popular OTA solution, has explicitly stated it does not support the New Architecture — and with its deprecation, it never will.

SwiftPatch fully supports React Native's New Architecture. This guide explains how the New Architecture works, why it creates OTA challenges, and how SwiftPatch solves them.

What Is the New Architecture?

React Native's New Architecture consists of three major components:

1. Fabric (New Renderer)

  • Synchronous rendering: UI updates happen synchronously on the main thread when needed
  • Concurrent rendering: Support for React 18 concurrent features (Suspense, transitions)
  • Reduced bridge overhead: Direct C++ communication instead of async JSON bridge
  • Better performance: Smoother animations and interactions

2. TurboModules (New Native Modules)

  • Lazy loading: Modules are loaded only when first accessed
  • Type-safe: CodeGen generates type-safe interfaces from TypeScript specs
  • Direct JSI access: No serialization/deserialization overhead
  • Faster startup: App starts faster because modules load on demand

3. JSI (JavaScript Interface)

  • Direct communication: JavaScript can call C++ functions directly
  • No bridge: Eliminates the async JSON bridge bottleneck
  • Shared memory: JavaScript and native code can share memory
  • Synchronous calls: Native calls can be synchronous when needed

Architecture Comparison

Old Architecture:
JS Thread ←→ [JSON Bridge (async)] ←→ Native Thread
                  ↑ Bottleneck

New Architecture:
JS Thread ←→ [JSI (direct, sync)] ←→ Native Thread
                  ↑ No bottleneck

The OTA Compatibility Challenge

The New Architecture creates specific challenges for OTA updates:

Challenge 1: CodeGen Dependencies

The New Architecture uses CodeGen to generate native code from TypeScript specifications. This means changes to TurboModule specs require regenerating native code — which can't be done via OTA.

// TurboModule spec (this generates native code)
export interface Spec extends TurboModule {
  multiply(a: number, b: number): number;
  // Adding a new method here requires native code regeneration
  // This CANNOT be updated via OTA
}

Challenge 2: Fabric Component Specs

Similar to TurboModules, Fabric components have native specs that generate native view managers. Changes to these specs require a new binary.

Challenge 3: JSI Binding Changes

If you use custom JSI bindings (C++ functions exposed to JavaScript), changes to those bindings require recompilation — not OTA updatable.

Challenge 4: Bridge vs Bridgeless Mode

React Native 0.76+ introduced bridgeless mode, which removes the legacy bridge entirely. OTA solutions that relied on the bridge for bundle loading needed to be updated.

Why CodePush Doesn't Support New Architecture

CodePush's architecture was built around the legacy React Native bridge:

  1. Bundle loading: CodePush intercepted the bridge's bundle loading mechanism to swap in updated bundles. Bridgeless mode removes this mechanism.
  2. Module registration: CodePush registered as a native module through the old module system. TurboModules work differently.
  3. Maintenance: With App Center's deprecation, there's no team maintaining CodePush to add New Architecture support.

This is a fundamental limitation, not a simple bug fix. Supporting the New Architecture would require a complete rewrite of CodePush's native code — which Microsoft has no plans to do.

How SwiftPatch Handles New Architecture

SwiftPatch was designed with the New Architecture in mind from the start:

JSI-Based Bundle Loading

Instead of relying on the legacy bridge, SwiftPatch uses JSI for bundle loading:

SwiftPatch Bundle Loading (New Architecture):

1. App starts → SwiftPatch native module initializes via TurboModule
2. SwiftPatch checks for cached update
3. If update exists → loads updated bundle via JSI
4. If no update → loads default bundle
5. JavaScript executes with new or default bundle

TurboModule Registration

SwiftPatch registers as a TurboModule, compatible with both the old and new module systems:

// SwiftPatch TurboModule Spec (internal)
export interface Spec extends TurboModule {
  initialize(config: Object): void;
  checkForUpdate(): Promise<Object | null>;
  downloadUpdate(): Promise<boolean>;
  applyUpdate(): void;
  getBundlePath(): string;
}

Fabric Compatibility

SwiftPatch doesn't render any UI components, so Fabric compatibility is inherent. The SDK operates entirely at the JavaScript engine level, below the renderer.

Bridgeless Mode Support

SwiftPatch fully supports bridgeless mode (React Native 0.76+):

Old Architecture:   SwiftPatch → Bridge → Bundle Loader ✓
New Architecture:   SwiftPatch → JSI → Bundle Loader ✓
Bridgeless Mode:    SwiftPatch → JSI → Bundle Loader ✓ (No bridge needed)

JavaScript vs. Native Code in New Architecture

Understanding what's OTA updatable in the New Architecture:

OTA Updatable (JavaScript Layer)

Everything in your JavaScript/TypeScript code that doesn't change native specs:

// ✅ OTA Updatable: Component rendering logic
function ProductCard({ product }: Props) {
  return (
    <View style={styles.card}>
      <Text style={styles.title}>{product.name}</Text>
      <Text style={styles.price}>{formatPrice(product.price)}</Text>
    </View>
  );
}

// ✅ OTA Updatable: Business logic
function calculateDiscount(price: number, tier: string): number {
  if (tier === 'premium') return price * 0.2;
  if (tier === 'basic') return price * 0.1;
  return 0;
}

// ✅ OTA Updatable: State management
const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: [], total: 0 },
  reducers: {
    addItem: (state, action) => {
      state.items.push(action.payload);
      state.total += action.payload.price;
    },
  },
});

// ✅ OTA Updatable: API calls
async function fetchProducts(): Promise<Product[]> {
  const response = await fetch('https://api.example.com/v2/products');
  return response.json();
}

// ✅ OTA Updatable: Styles
const styles = StyleSheet.create({
  card: { padding: 16, borderRadius: 12, backgroundColor: '#fff' },
  title: { fontSize: 18, fontWeight: 'bold' },
  price: { fontSize: 16, color: '#059669' },
});

NOT OTA Updatable (Native Layer)

Changes to native specs, native code, or platform configuration:

// ❌ NOT OTA Updatable: Adding new TurboModule methods
// (requires CodeGen to regenerate native code)
export interface Spec extends TurboModule {
  existingMethod(): void;
  newMethod(): void; // Adding this requires new binary
}

// ❌ NOT OTA Updatable: New Fabric component props
// (requires CodeGen to regenerate native code)
export interface NativeProps extends ViewProps {
  existingProp: string;
  newProp: number; // Adding this requires new binary
}

The Gray Area: Using Existing Native Methods Differently

This is OTA updatable because you're only changing the JavaScript call pattern, not the native interface:

// ✅ OTA Updatable: Different usage of existing native methods
// The TurboModule spec hasn't changed, only how JS calls it

// Before
useEffect(() => {
  NativeAnalytics.track('screen_view', { screen: 'home' });
}, []);

// After (changed what we track - JS only change)
useEffect(() => {
  NativeAnalytics.track('screen_view', {
    screen: 'home',
    timestamp: Date.now(),
    session_id: getSessionId(),
  });
}, []);

Setup Guide for New Architecture Projects

Step 1: Ensure New Architecture Is Enabled

# iOS: Check Podfile
# newArchEnabled should be true
cat ios/Podfile | grep newArchEnabled

# Android: Check gradle.properties
cat android/gradle.properties | grep newArchEnabled

Step 2: Install SwiftPatch

npm install swiftpatch

Step 3: iOS Setup

cd ios && pod install && cd ..

SwiftPatch's podspec includes New Architecture support automatically. The TurboModule spec is pre-configured.

Step 4: Android Setup

SwiftPatch auto-links on React Native 0.60+. For New Architecture, the TurboModule is registered automatically.

Step 5: Initialize

// Same initialization as always
import { SwiftPatch } from 'swiftpatch';

SwiftPatch.init({
  deploymentKey: 'YOUR_DEPLOYMENT_KEY',
  autoRollback: true,
  checkFrequency: 'ON_APP_RESUME',
});

No additional configuration needed for New Architecture. SwiftPatch automatically detects whether your app uses the old or new architecture and configures itself accordingly.

Step 6: Verify

npx react-native run-ios --configuration Release

Check logs for:

[SwiftPatch] Architecture: New (Fabric + TurboModules)
[SwiftPatch] Bridgeless: true
[SwiftPatch] Initialized successfully

Bridgeless Mode Support

React Native 0.76 introduced bridgeless mode as the default. SwiftPatch has full bridgeless support:

Bridge Mode Detection:

App Start → SwiftPatch checks environment
  → Bridge present? → Use bridge-based loading
  → Bridgeless? → Use JSI-based loading (default for 0.76+)

No Configuration Changes Needed

// Same code for bridge and bridgeless
SwiftPatch.init({
  deploymentKey: 'YOUR_KEY',
  autoRollback: true,
});
// SwiftPatch handles the difference automatically

React Native 0.76+ Compatibility

SwiftPatch is tested against every React Native release. Current compatibility:

React Native VersionArchitectureSwiftPatch Support
0.60 - 0.67Old ArchitectureFull Support
0.68 - 0.71Old + New (opt-in)Full Support
0.72 - 0.75Old + New (opt-in)Full Support
0.76+New Architecture (default)Full Support

What Changed in 0.76+

  • New Architecture is now the default
  • Bridgeless mode is enabled by default
  • React 18 features (concurrent rendering) are fully available
  • InteropLayer provides backward compatibility for old modules

SwiftPatch works with all of these changes out of the box.

Testing OTA Updates with New Architecture

Testing Checklist

New Architecture OTA Testing:

[ ] Deploy update that changes only JS logic
[ ] Verify update downloads and applies correctly
[ ] Test with Fabric components (rendering works post-update)
[ ] Test with TurboModules (native calls work post-update)
[ ] Test automatic rollback triggers correctly
[ ] Test on bridgeless mode
[ ] Test on devices with old architecture (backward compat)
[ ] Verify bundle integrity check passes
[ ] Test on React Native 0.76+
[ ] Test with concurrent features (Suspense, useTransition)

Debugging Tips

// Enable verbose logging for debugging
SwiftPatch.init({
  deploymentKey: 'YOUR_KEY',
  logLevel: 'verbose', // 'verbose' | 'info' | 'warn' | 'error'
});

Verbose logs will show:

[SwiftPatch] Architecture detected: New Architecture (Fabric + TurboModules)
[SwiftPatch] Bridge mode: Bridgeless
[SwiftPatch] Bundle loading mechanism: JSI
[SwiftPatch] Checking for updates...
[SwiftPatch] Update available: v2.3.1 (patch size: 187KB)
[SwiftPatch] Downloading patch...
[SwiftPatch] Download complete. Verifying integrity...
[SwiftPatch] Bundle hash verified ✓
[SwiftPatch] Bundle signature verified ✓
[SwiftPatch] Update staged for next restart.

FAQ

Q: Does SwiftPatch work with React Native 0.76's default New Architecture?

A: Yes. SwiftPatch has supported the New Architecture since our 2.0 release. No additional configuration is needed.

Q: Can I update Fabric components via OTA?

A: Yes, as long as you're not changing the native component spec. Changes to the JavaScript rendering logic, props handling, and styles are fully OTA updatable.

Q: Can I update TurboModule JavaScript code via OTA?

A: Yes, as long as you're not changing the TurboModule spec (the interface definition that CodeGen uses). Changes to how you call existing TurboModule methods are OTA updatable.

Q: What about the InteropLayer for old modules?

A: SwiftPatch works with modules using the InteropLayer (old-style modules running in New Architecture). Updates to JavaScript code calling these modules are OTA updatable.

Q: Does bridgeless mode affect OTA updates?

A: No. SwiftPatch uses JSI-based bundle loading that works in both bridge and bridgeless modes. The switch is transparent.

Q: I'm migrating to New Architecture. Do I need to change my SwiftPatch setup?

A: No. SwiftPatch automatically detects the architecture and configures itself. Your existing setup will continue to work after migrating to the New Architecture.

Q: Can I use OTA updates during the migration to New Architecture?

A: Yes. During migration, you may have some components on the old architecture and some on new. SwiftPatch handles both simultaneously.

Q: Does SwiftPatch support React 18 concurrent features?

A: Yes. Concurrent rendering (Suspense, useTransition, useDeferredValue) is fully compatible with SwiftPatch OTA updates.

Q: What about Hermes bytecode compatibility?

A: SwiftPatch generates differential patches at the Hermes bytecode level when Hermes is enabled, ensuring optimal patch sizes for compiled bundles.

Q: Can I use custom JSI bindings with SwiftPatch?

A: Yes. Custom JSI bindings are native code and won't be affected by OTA updates. Your JavaScript code that calls those bindings is OTA updatable.

Conclusion

React Native's New Architecture is the future of the framework, and your OTA solution needs to keep up. CodePush can't. Expo Updates requires Expo.

SwiftPatch supports the New Architecture natively — Fabric, TurboModules, JSI, bridgeless mode, and React Native 0.76+ — with zero additional configuration.

The same simple setup, the same powerful features, the same peace of mind. Whether you're on the old architecture or the new one, SwiftPatch just works.

Get started with SwiftPatch for free →

Ready to ship updates faster?

Get started with SwiftPatch for free. No credit card required.

Join Waitlist

Related Articles