A lightweight OTA (Over-The-Air) update solution for React Native apps. It allows you to update your JavaScript code without going through App Store or Play Store releases.
- OTA (Over-The-Air) Updates: Updates pushed remotely to the app without requiring a new version from the App Store / Play Store.
- JS Bundle: The compiled version of your React Native JavaScript code. It can generated using the following commands.
- Android:
react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res
- iOS:
react-native bundle --platform ios --dev false --entry-file index.js --bundle-output ios/main.jsbundle
- Android:
β οΈ Note:react-native-simple-ota
only updates JavaScript code, not native modules.- Works only in release builds; in debug mode, Metro bundler is used instead.
react-native-simple-ota
stores and loads custom JS bundles (OTA updates) from the local device storage.- OTA updates are applied on the next app launch.
- You control when and how to download OTA updates by implementing a custom
OtaUpdateProvider
. This ensures full flexibility for caching, version control, and download logic. - On app launch, the library simply redirects the JS bundle loader to your downloaded OTA file (if available).
react-native-simple-ota
works with React Native Old & New Architecture and Expo Bare Work Flow.
- Host your JS Bundles on to CDNs for faster delivery.
- Host a compressed version of the JavaScript bundle, as these files can be quite large.
- Validate hash of JS Bundle after download to avoid corruption.
- Take a look at the example project for reference.
npm install react-native-simple-ota
import {
getBundleVersion,
type OtaUpdate,
type OtaUpdateProvider,
} from 'react-native-simple-ota';
class MyOtaUpdateProvider implements OtaUpdateProvider {
isUserApplicableForUpdate(): boolean {
return true;
}
async getOtaUpdate(): Promise<OtaUpdate | null> {
try {
const bundleInfo: BundleInfo = await this.getUpdate();
const update: OtaUpdate = {
bundleVersion: bundleInfo.bundle_version,
bundlePath: bundlePath,
};
return update;
} catch (e: any) {
}
return null;
}
}
import { init } from 'react-native-simple-ota';
init(new MyOtaUpdateProvider());
import {
applyOTAIfApplicable,
} from 'react-native-simple-ota';
// ...
useEffect(() => {
applyOTAIfApplicable();
}, []);
Note: Though the below file are in Kotlin and Swift they can be used in Java and Objective C respectively
- Android
The Application class might not exist in your Android code so you'll have to create it at
myapp/android/app/src/main/java
and add it in manifest file.
import android.app.Application
import dev.droid.simpleota.ReactNativeSimpleOtaModule
class MainApplication : Application(), ReactApplication {
override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages
override fun getJSMainModuleName(): String = "index"
override fun getJSBundleFile(): String? {
return ReactNativeSimpleOtaModule.getJSBundleFile(this@MainApplication) ?: super.getJSBundleFile()
}
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
}
}
- iOS
import UIKit
import React
import React_RCTAppDelegate
import ReactAppDependencyProvider
import ReactNativeSimpleOta
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var reactNativeDelegate: ReactNativeDelegate?
var reactNativeFactory: RCTReactNativeFactory?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
let delegate = ReactNativeDelegate()
let factory = RCTReactNativeFactory(delegate: delegate)
delegate.dependencyProvider = RCTAppDependencyProvider()
reactNativeDelegate = delegate
reactNativeFactory = factory
window = UIWindow(frame: UIScreen.main.bounds)
factory.startReactNative(
withModuleName: "SimpleOtaExample",
in: window,
launchOptions: launchOptions
)
return true
}
}
class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
override func sourceURL(for bridge: RCTBridge) -> URL? {
self.bundleURL()
}
override func bundleURL() -> URL? {
#if DEBUG
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#else
if let customBundlePath = ReactNativeSimpleOta.getJSBundleFile() {
return URL(fileURLWithPath: customBundlePath)
}
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
#endif
}
}