Over-the-Air Updates with React Native and CodePush

React Native enables cross-platform mobile development with JavaScript, but shipping fixes and features through app store review cycles can delay delivery by days or weeks. Over-the-air (OTA) updates bypass that bottleneck by patching the JavaScript bundle and assets directly on the user’s device. Microsoft’s CodePush is the most established OTA service for React Native, offering fast, scalable updates without requiring a full app release. This guide walks through every stage of adopting CodePush, from initial setup to production rollout strategies, so your team can ship changes confidently and continuously.

What Is CodePush?

CodePush is a cloud service that hosts incremental updates for React Native (and Cordova) apps. When a user launches an app configured with CodePush, the SDK checks the configured deployment key against the remote server. If a newer version of the JavaScript bundle or assets exists, the update is downloaded silently in the background and applied on the next restart (or immediately, depending on configuration).

The core value proposition is speed. Instead of waiting for Apple or Google approval for every minor tweak, a team can push a bug fix within minutes. CodePush also provides granular control over rollout percentages, mandatory update flags, and rollback capabilities, making it a robust choice for teams that need both agility and safety.

Though Microsoft acquired and then deprecated the standalone App Center service, CodePush itself remains functional and widely used. The open-source react-native-code-push package is maintained by Microsoft’s open-source team and the community. For newer projects, you might also consider Expo Updates (if using Expo) or EAS Update, but CodePush remains a strong option for bare React Native workflows.

Prerequisites

Before integrating CodePush, ensure you have the following ready:

  • A React Native project (bare workflow, not Expo Go – though Expo bare projects work).
  • Node.js 16 or later and npm/yarn.
  • A Microsoft App Center account (free tier available). App Center hosts your CodePush deployments.
  • React Native version 0.60 or newer (linking via autolinking is supported).
  • Familiarity with React Native build commands and your project’s code-push deployment keys.

Setting Up App Center and the CodePush CLI

Start by creating an App Center account. Log in to appcenter.ms and create a new app. Select your platform (Android or iOS) and the “React Native” framework. You will need separate apps for each platform unless you manage deployments manually.

Install the CodePush CLI globally:

npm install -g appcenter-cli

Then log in:

appcenter login

This opens a browser window to authenticate. Once logged in, register your app with CodePush. If you created an App Center app named “MyApp-Android,” run:

appcenter apps create -d MyApp-Android -o Android -p React-Native

This creates the app in App Center and generates default Staging and Production deployment keys. You can view them with:

appcenter codepush deployment list -a MyApp-Android -k

The output shows keys like StagingKey and ProductionKey. Keep these secure – they are how your app identifies which update stream to follow.

For iOS, repeat the process (e.g., MyApp-iOS). You can also create additional custom deployments for testing or canary releases.

Integrating the CodePush SDK into Your React Native App

Installation

Install the SDK package:

npm install react-native-code-push

If you are using React Native 0.60+, autolinking handles native module linkage. For older versions, run npx react-native link react-native-code-push.

iOS Configuration

In Xcode, open your project and locate the Info.plist file. Add a new key: CodePushDeploymentKey with the string value of your deployment key (typically the Production key for Release builds, but you can set per-configuration). Alternatively, you can set the key in the AppDelegate.m using the codePushDeploymentKey method.

If you use CocoaPods, the SDK is linked automatically after pod install. Ensure your Podfile includes use_frameworks! if needed.

Android Configuration

In android/app/build.gradle, add the following inside defaultConfig:

resValue "string", "CodePushDeploymentKey", "YOUR_DEPLOYMENT_KEY"

You can place platform-specific keys in build types (debug, release). Also, verify that your MainApplication.java includes the CodePush package:

new CodePush(getResources().getString(R.string.CodePushDeploymentKey), getApplicationContext(), BuildConfig.DEBUG)

This is usually added by autolinking, but double-check if you get errors.

Wrapping the Root Component

In your App.js or root component, import CodePush and define options:

import codePush from "react-native-code-push";

Create a configuration object:

let codePushOptions = {
  checkFrequency: codePush.CheckFrequency.ON_APP_START,
  installMode: codePush.InstallMode.ON_NEXT_RESUME,
  mandatoryInstallMode: codePush.InstallMode.IMMEDIATE
};

Then export the wrapped component:

export default codePush(codePushOptions)(App);

The checkFrequency controls when to check for updates: ON_APP_START (default), ON_APP_RESUME, or MANUAL (you invoke codePush.sync() yourself). installMode defines when the update is applied after download: IMMEDIATE, ON_NEXT_RESUME, or ON_NEXT_RESTART.

Advanced Configuration and Deployment Keys

Most apps need separate keys for debug and release builds. A common approach is to use the Staging key for debug builds and the Production key for release builds. You can set this via build configurations:

  • iOS: Use #if DEBUG in AppDelegate.m to conditionally set the key.
  • Android: Use buildTypes in build.gradle to assign different resValue strings.

Alternatively, you can hardcode the key in code but that risks exposing it in version control. Environment variables and a .env file (with react-native-config) offer better security.

The mandatoryInstallMode is crucial: when you mark an update as mandatory (via the CLI flag --mandatory), the SDK applies it IMMEDIATE regardless of other settings. Use this sparingly for critical fixes.

Publishing Updates with the CLI

The primary command to release an update is:

appcenter codepush release-react -a <appName> -d <deploymentName>

For example, to release to the Staging deployment of your Android app:

appcenter codepush release-react -a MyApp-Android -d Staging

This command runs a production build of the React Native bundle, then uploads it. You can customize the build using these options:

  • --p / --platform: (auto-detected)
  • --bundle-name: custom name for the bundle file.
  • --output-dir: where to save the intermediate bundle (useful for debugging).
  • --sourcemap-output: generate a source map for error reporting.
  • --description: a release note shown in the App Center dashboard.
  • --mandatory: forces the update immediately.
  • --rollout: percentage of users who receive the update (e.g., --rollout 25 for 25%).
  • --disable-duplicate-release-error: allow re-releasing the same version if needed.

You can also release with a specific bundle version using --target-binary-version. This ensures the update only applies to apps running a certain native version. For example, --target-binary-version “>=1.0.0 <2.0.0”.

To publish to Production after testing in Staging, simply run the same command with -d Production.

Managing Updates, Rollouts, and Rollbacks

App Center provides a web dashboard for managing your CodePush deployments. From there you can:

  • View release history and download counts.
  • Promote an update from Staging to Production without rebuilding.
  • Roll back a release to a previous version instantly.
  • Modify rollout percentage mid-release (increases go live immediately; decreases affect new devices).

Rolling back is simple: click “Rollback” on the release in the App Center UI. This sets the current release as inactive and returns users to the previous one. However, it does not revert the bundle on devices that already installed the rolled-back update – those devices stay on that version until the next update. To force a reversion, you must release a newer update that reverts the code.

Staged rollouts are essential for safety. Start with 1–5% of users, monitor crash reports and analytics, then increase to 25%, 50%, and 100%. If you detect a critical bug, roll back immediately and release a fix. This pattern is standard practice for OTA updates.

Best Practices for Production Use

Testing Updates Thoroughly

Before pushing to production, always release to a Staging environment (or a custom “Beta” deployment) and test on real devices. CodePush supports deployment key swapping in-app, but the easiest way is to use separate builds: debug builds use Staging, release builds use Production. You can also use the appcenter codepush promote command to promote a Staging release to Production after verification.

Bundle Versioning and Semantic Versioning

Use --target-binary-version to scope updates to specific native versions. When you release a new version of your app (through app stores), bump the native version (e.g., 1.0.0 to 1.1.0). Then stop releasing OTA updates that target the old version. This prevents users on old native code from receiving incompatible OTA patches.

Bundle Size and Performance

The release-react command already optimizes the bundle, but consider using --sourcemap-output to debug errors. Keep the bundle size under a few megabytes for quick downloads on slow networks. Use --output-dir to inspect the generated bundle and assets. Remove unnecessary libraries from the bundle if you only update JS code.

User Experience and Notifications

CodePush downloads in the background silently by default. You can customize the experience with updateDialog:

export default codePush({
  updateDialog: {
    title: “Update available”,
    optionalUpdateMessage: “An update is ready. Install?”,
    mandatoryContinueButtonLabel: “Update now”
  }
})(App);

Use this sparingly; mandatory updates should be rare. For non-critical updates, let the install happen on next resume to avoid interrupting the user.

Security Considerations

CodePush uses HTTPS and signs the updates. However, deployment keys are considered secrets. Do not commit hardcoded keys to public repositories. Use environment variables, build-time injection, or a secrets manager. Also, consider restricting App Center access to your team and using service principals instead of personal accounts for CI/CD.

Alternatives and Comparison

While CodePush is battle-tested, it is not the only OTA solution for React Native. Two notable alternatives have emerged:

  • Expo Updates (formerly Expo Releases): Built into the Expo ecosystem. If you use managed Expo workflow, this is simpler and integrates with EAS Build. It supports over-the-air JS updates and asset updates. However, it requires Expo SDK.
  • CodePush (Microsoft) remains the most flexible option for bare React Native projects that do not use Expo. It works with Android, iOS, and even Windows/macOS targets.
  • Self-hosted OTA: For teams that need full control, you can host your own update server using the open-source code-push-server. This avoids App Center dependency but adds operational overhead.

Choosing between them depends on your project setup. If you are committed to bare React Native without Expo, CodePush is the most mature option. If you use Expo, EAS Update is the recommended path.

Conclusion

CodePush enables React Native developers to deliver hot fixes and new features without waiting for app store approval, drastically shortening the feedback loop. By following the setup, configuration, and release management steps outlined here, you can integrate CodePush into your CI/CD pipeline and push updates confidently. Always test on a Staging deployment, use staged rollouts, and apply mandatory updates only when absolutely necessary. With these practices, your users will experience faster improvements and fewer critical bugs.