OTA updates with CodePush: patterns, pitfalls, and rollback
· 4 min read · Amrith Vengalath
- React Native
- CodePush
- OTA
- CI/CD
The pitch for over-the-air updates is irresistible: a critical bug ships, you push a JavaScript fix, and users have it within the hour without waiting days for app review. We leaned on CodePush heavily for exactly this. But OTA is a loaded gun pointed at your entire user base - the same mechanism that pushes a fix to everyone in minutes can push a crash to everyone in minutes. Treating it carelessly is how you turn a small bug into a company-wide incident.
Here's how we used it without setting ourselves on fire.
What OTA can and can't do
CodePush updates the JavaScript bundle and assets. It cannot change native code. So a JS fix, a copy change, a tweaked screen - yes. A new native dependency, a permission change, anything touching the native projects - no, that's a store release. Crossing that line is the most common OTA mistake: pushing a bundle that expects native code the installed app doesn't have, which crashes on launch.
This is why CodePush ties releases to a target binary version. An update goes only to users on a compatible app version. Get that targeting wrong and you ship JS to a binary that can't run it.
Staged rollout, always
The rule we never broke: never push to 100% immediately. CodePush lets you roll out to a percentage of users.
# go to 10% first
appcenter codepush release-react -a org/MyApp-iOS -d Production --rollout 10Watch the crash dashboards for that 10% for a while. If crash-free rate holds, bump it:
appcenter codepush patch -a org/MyApp-iOS Production --rollout 100The 10% is your canary. If the update is bad, you've hurt a tenth of your users for a short window instead of all of them. That difference is the entire safety story.
Automatic rollback
CodePush has a quietly brilliant feature: if an update causes the app to crash on startup before it finishes initializing, the client rolls back to the previous bundle automatically on the next launch. For this to work you have to tell CodePush the update was successful once your app is genuinely up and running:
import codePush from "react-native-code-push";
// after the app has successfully started and rendered
codePush.notifyAppReady();If notifyAppReady() is never called - because the app crashed before reaching it - CodePush assumes the update broke things and reverts. Skipping this call breaks the whole safety net, and it's an easy thing to forget. It's the most important one line in your CodePush integration.
Mandatory vs optional updates
For a critical fix you can mark an update mandatory so users get it immediately, even mid-session on next resume. For routine updates, optional and applied on next restart is gentler. We reserved mandatory for actual fixes - security issues, crash fixes - and let everything else flow in quietly. Mandatory-everything trains users to expect interruptions and isn't worth it.
The pitfalls we actually hit
- Forgetting
notifyAppReadyduring a refactor, which silently disabled auto-rollback. We only noticed because a teammate asked why a known-bad test push didn't revert. Now it's something we check. - Targeting the wrong binary version, pushing JS that referenced a native module only present in a newer build. The version targeting exists precisely to prevent this; respect it.
- Treating OTA as a substitute for store releases. It's for fixes and small changes between releases, not a way to avoid the stores entirely. Bundles drift further from the binary the longer you go, and the risk compounds.
The takeaway
OTA is fantastic and a little dangerous, in proportion. The discipline is simple and non-negotiable: stage every rollout, keep notifyAppReady honest so rollback works, respect binary version targeting, and reserve mandatory for real emergencies. Do that and OTA is one of the best things about React Native. Skip it and you'll eventually push a bad bundle to everyone at once and learn all of this the hard way.
(Worth noting, since the ground is shifting: the hosted App Center service that backs this is on a sunset path, so if you're starting fresh it's worth knowing the alternatives - a topic for another post. The patterns here apply whatever provider you land on.)