OTA Updates for Bare React Native (Without Expo)
How to add OTA updates to bare React Native projects without Expo. Complete setup guide for teams not using the Expo ecosystem.
Introduction: The Bare React Native Challenge
If you're running a bare React Native project (created with npx react-native init or ejected from Expo), you've probably noticed that most OTA update documentation assumes you're using Expo. Expo Updates is deeply integrated into the Expo ecosystem, and many guides simply don't apply to bare projects.
Meanwhile, CodePush — the go-to solution for bare React Native OTA updates — is being deprecated with the App Center shutdown. This leaves bare React Native developers in a tough spot.
SwiftPatch was built for exactly this scenario. From day one, SwiftPatch has been designed to work seamlessly with bare React Native projects — no Expo required, no compromises.
Why Bare React Native Projects Need OTA
- Full native control: Direct access to native code, no abstraction layer
- Custom native modules: Freedom to integrate any native SDK
- Build flexibility: Custom build processes, multiple build variants
- Performance optimization: Fine-grained control over native performance
- Enterprise requirements: Specific native integrations mandated by the business
But choosing bare React Native shouldn't mean giving up on modern deployment capabilities. OTA updates are just as valuable — if not more — for bare projects, which often have complex build pipelines where reducing App Store submissions saves significant time.
SwiftPatch: Built for Bare React Native
Unlike Expo Updates (which requires the Expo ecosystem) or CodePush (which is being deprecated), SwiftPatch was designed specifically for bare React Native from the beginning:
- Zero Expo dependencies: No need for
expo,expo-updates, or any Expo packages - Auto-linking support: Works with React Native 0.60+ auto-linking
- Minimal native changes: Near-zero configuration for iOS and Android
- Custom native module compatible: Works alongside any native modules
- New Architecture support: Full support for Fabric and TurboModules
Step-by-Step Setup Guide
Prerequisites
- React Native 0.60 or higher
- Node.js 16 or higher
- Xcode 14+ (for iOS)
- Android Studio (for Android)
- CocoaPods (for iOS)
Step 1: Install the SwiftPatch SDK
npm install swiftpatch
Step 2: Install the SwiftPatch CLI
npm install -g swiftpatch-cli
swiftpatch login
Step 3: Create Your App on SwiftPatch
swiftpatch app create --name "MyBareApp" --platform all
Step 4: Initialize in Your App Code
// App.tsx
import React from 'react';
import { SwiftPatch } from 'swiftpatch';
// Initialize SwiftPatch as early as possible
SwiftPatch.init({
deploymentKey: 'YOUR_DEPLOYMENT_KEY',
autoRollback: true,
checkFrequency: 'ON_APP_RESUME',
});
function App() {
return (
// Your app components
);
}
export default App;
That's the JavaScript side done. Now let's configure the native projects.
iOS Configuration (CocoaPods)
Step 1: Install Pods
cd ios && pod install && cd ..
SwiftPatch's podspec is automatically detected by React Native's auto-linking. You should see SwiftPatch in the pod install output.
Step 2: Verify AppDelegate (Optional)
For most projects, no changes to AppDelegate are needed. SwiftPatch uses method swizzling to automatically intercept bundle loading.
However, if you have a custom bundle loading setup, you can manually configure it:
// AppDelegate.mm (only if you have custom bundle loading)
#import <SwiftPatch/SwiftPatch.h>
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
return [SwiftPatch bundleURL];
}
For Swift-based AppDelegates:
// AppDelegate.swift (only if you have custom bundle loading)
import SwiftPatch
override func sourceURL(for bridge: RCTBridge) -> URL? {
return SwiftPatch.bundleURL()
}
Step 3: Verify Build Settings
Ensure your Release build configuration generates a JavaScript bundle. This is the default for React Native projects, but verify in your Xcode build phases:
Build Phases → Bundle React Native code and images
This script should be present and active for Release builds.
Android Configuration (Gradle)
Step 1: Verify Auto-Linking
SwiftPatch auto-links on React Native 0.60+. Verify by checking that the package is listed:
npx react-native config
You should see swiftpatch in the dependencies output.
Step 2: Verify MainApplication (Optional)
Similar to iOS, most projects don't need any changes. SwiftPatch automatically configures itself.
If you have a custom getJSBundleFile implementation:
// MainApplication.java (only if you have custom bundle loading)
import com.swiftpatch.SwiftPatch;
@Override
protected String getJSBundleFile() {
return SwiftPatch.getBundlePath();
}
For Kotlin projects:
// MainApplication.kt (only if you have custom bundle loading)
import com.swiftpatch.SwiftPatch
override fun getJSBundleFile(): String? {
return SwiftPatch.getBundlePath()
}
Step 3: Verify ProGuard Rules
If you use ProGuard (code shrinking), SwiftPatch automatically adds the necessary rules. Verify by checking:
android/app/build.gradle
# SwiftPatch ProGuard rules are added automatically via consumer rules
Verifying the Installation
Run on iOS Simulator
npx react-native run-ios --configuration Release
Check the Xcode console for:
[SwiftPatch] Initialized with deployment key: YOUR_KEY
[SwiftPatch] Checking for updates...
[SwiftPatch] App is up to date.
Run on Android Emulator
npx react-native run-android --variant=release
Check the Android logcat for:
SwiftPatch: Initialized with deployment key: YOUR_KEY
SwiftPatch: Checking for updates...
SwiftPatch: App is up to date.
Verification Checklist
[ ] iOS pod install completes without errors
[ ] Android build completes without errors
[ ] SwiftPatch logs appear in console on launch
[ ] "App is up to date" message confirmed
[ ] No crashes on fresh install
First Deployment
Step 1: Make a Visible Change
Make a small, visible change to your app (like changing a text label) so you can verify the OTA update works.
Step 2: Deploy
swiftpatch release --platform all --description "First OTA update - changed welcome text"
Step 3: Verify on Device
- Open your app (still shows old text)
- Close the app completely
- Reopen the app
- The new text should appear
# Check deployment status
swiftpatch status
# Expected output:
# Latest Deployment:
# Platform: all
# Status: Active
# Rollout: 100%
# Adoption: Pending first update check
Working with Native Modules
One of the biggest advantages of bare React Native is the ability to use custom native modules. SwiftPatch works seamlessly alongside them.
JavaScript-Only Native Module Changes
If your native module's JavaScript interface changes (but the native code doesn't), it's OTA updatable:
// This change is OTA updatable
// (only the JS wrapper changed, native code is the same)
import { NativeModules } from 'react-native';
const { MyAnalytics } = NativeModules;
// Before
export function trackEvent(name: string) {
MyAnalytics.track(name);
}
// After (added parameter - JS only change)
export function trackEvent(name: string, properties?: Record<string, any>) {
MyAnalytics.track(name, properties ?? {});
}
Native Code Changes
If the native module's native code changes, you need a new binary:
Scenario: Adding a new native method
1. Update native code (requires new binary)
2. Submit to App Store / Google Play
3. After approval, use OTA to update JS code that calls the new method
4. Target the OTA update to the new binary version:
swiftpatch release --platform all --target-binary ">=2.1.0" --description "Use new analytics native method"
Binary Version Targeting
Binary version targeting ensures OTA updates only reach devices running compatible native code:
# Only users on binary 2.x get this update
swiftpatch release --target-binary "2.*"
# Only users on 2.1.0 or higher
swiftpatch release --target-binary ">=2.1.0"
# Users on 2.0.x (but not 2.1.x)
swiftpatch release --target-binary "~2.0.0"
React Native New Architecture Support (Fabric)
SwiftPatch fully supports React Native's New Architecture:
- Fabric renderer: OTA updates work with Fabric components
- TurboModules: JavaScript interfaces are OTA updatable
- JSI (JavaScript Interface): Compatible with JSI-based bridges
- Bridgeless mode: Full support for bridgeless React Native
// New Architecture setup is identical
SwiftPatch.init({
deploymentKey: 'YOUR_KEY',
autoRollback: true,
// Works with both old and new architecture
});
No additional configuration is needed for New Architecture projects.
Migrating from CodePush in Bare RN
If you're currently using CodePush in a bare React Native project, SwiftPatch provides a migration script:
Automated Migration
# Run the migration script
npx swiftpatch migrate --from codepush
# The script will:
# 1. Detect your CodePush configuration
# 2. Remove CodePush native code
# 3. Install SwiftPatch native code
# 4. Convert deployment keys
# 5. Update your JavaScript initialization code
Manual Migration
If you prefer manual control:
# Step 1: Remove CodePush
npm uninstall react-native-code-push
cd ios && pod install && cd ..
# Step 2: Install SwiftPatch
npm install swiftpatch
cd ios && pod install && cd ..
# Step 3: Update your JS code
// Before (CodePush)
import codePush from 'react-native-code-push';
const App = () => { /* ... */ };
export default codePush(App);
// After (SwiftPatch)
import { SwiftPatch } from 'swiftpatch';
SwiftPatch.init({
deploymentKey: 'YOUR_SWIFTPATCH_KEY',
autoRollback: true,
});
const App = () => { /* ... */ };
export default App;
Migration Checklist
[ ] Remove react-native-code-push package
[ ] Remove CodePush native configuration (iOS & Android)
[ ] Install swiftpatch package
[ ] Run pod install for iOS
[ ] Update JavaScript initialization
[ ] Replace deployment keys
[ ] Test on iOS simulator (Release build)
[ ] Test on Android emulator (Release build)
[ ] Deploy first update via SwiftPatch
[ ] Verify update is received on device
Common Issues and Troubleshooting
Issue: Pod Install Fails
# Solution: Clean and reinstall
cd ios
pod deintegrate
pod cache clean --all
pod install
cd ..
Issue: Android Build Fails with "Could not find"
# Solution: Clean Gradle cache
cd android
./gradlew clean
cd ..
npx react-native run-android --variant=release
Issue: Update Not Applied After Deploy
Common causes:
1. App was not restarted after download
→ Close app completely and reopen
2. Running in Debug mode
→ OTA only works in Release mode
3. Deployment key mismatch
→ Verify key matches your SwiftPatch app
Issue: "No Bundle URL Present" on iOS
# Solution: Ensure bundle is generated
# Check Build Phases in Xcode for:
# "Bundle React Native code and images" script
Issue: Slow First Update Check
The first update check after installation may be slower as the SDK initializes. Subsequent checks are cached and near-instant.
Getting Help
# Generate a diagnostic report
swiftpatch doctor
# This checks:
# - SDK version
# - Native configuration
# - Deployment key validity
# - Network connectivity
# - Bundle integrity
Conclusion
Bare React Native projects deserve first-class OTA update support. SwiftPatch delivers exactly that — zero Expo dependencies, minimal configuration, and full compatibility with custom native modules and the New Architecture.
Whether you're migrating from CodePush or adding OTA updates for the first time, SwiftPatch makes it straightforward:
- Install the SDK
- Add one initialization call
- Run pod install / gradle sync
- Deploy your first update
No Expo required. No compromises.
Ready to ship updates faster?
Get started with SwiftPatch for free. No credit card required.
Join WaitlistRelated Articles
Expo EAS Update Pricing: Cost, Bandwidth & What It Really Costs at Scale
Expo EAS Update feels cheap when you're starting out. Then your app grows. Here's how Expo actually bills for EAS Update, why costs grow faster than you expect, and what happens when you're shipping frequent releases to real users.
ComparisonExpo EAS Update Alternative — Best Expo Updates Replacement with Patch Updates
Expo EAS Update works well inside the Expo ecosystem. But as apps scale, teams need patch updates, rollback, internal testing, bare React Native support, and on-premise hosting. Here's how SwiftPatch compares.
GuidePatch Updates: The Modern CodePush Alternative
Microsoft CodePush is deprecated. Learn how to migrate to SwiftPatch, the modern OTA update platform for React Native with 98% smaller patches, automatic rollback, and enterprise security.