Microsoft retired the hosted Visual Studio App Center service on March 31, 2025. That included the managed service many React Native teams knew through CodePush, although Microsoft also released a self-hostable CodePush server.
The service retirement did not remove the underlying need. Teams still want to fix JavaScript bugs, adjust UI, update copy, and ship compatible asset changes without waiting for a new App Store or Play Store release.
But choosing a replacement is not just a matter of finding a familiar sync() call. An OTA system becomes part of your production runtime and your release process. The important questions are about compatibility, failure handling, rollout control, and how much of the system your team wants to operate.
What an OTA system actually has to manage
An OTA update is not simply a file download. It is a decision about which code a particular installed binary can safely execute.
Native and runtime compatibility
React Native lets you change JavaScript without rebuilding the native application, but only while that JavaScript remains compatible with the native code already on the device.
Adding a native module, changing permissions, upgrading React Native, switching important native configuration, or changing a JavaScript engine can require a new binary. A production OTA system therefore needs a compatibility boundary—often called a runtime version—and must refuse to deliver an update across that boundary.
This is one of the easiest details to overlook in a proof of concept and one of the most important details in production.
Channels and release tracks
Most teams need more than one stream of updates. You may want production, beta, QA, internal, or customer-specific tracks.
A channel is useful only if its relationship to builds and updates is explicit. Teams should know which installed binaries read from which channel, how an update moves between channels, and whether promoting a tested release preserves its identity.
Safe rollout strategies
Sending an update to every eligible device at once is fast, but it gives you very little time to react.
Percentage-based rollouts let you expose a release gradually. Targeted rollouts go further by limiting eligibility using properties such as plan, region, or an internal beta flag. These controls solve different problems: targeting defines who is eligible, while staging controls how much of that group receives the update.
Rollback and recovery
A dashboard rollback is helpful, but device-side recovery matters too. Consider what happens when an update crashes before the app can check the server again.
A robust design should define:
- when an update is considered healthy;
- how repeated launch failures are detected;
- whether the device can return to a last-known-good bundle;
- whether the embedded bundle remains a final fallback; and
- how a failed update is prevented from being retried on the same device.
The worst time to discover that rollback depends on a successful JavaScript launch is after shipping JavaScript that cannot launch.
Integrity verification
The client should verify that the downloaded artifact is exactly the artifact the service intended to deliver. That normally means secure transport plus cryptographic hashes or signatures, validation before activation, and an atomic transition so a partial install never becomes the active version.
Integrity and compatibility are separate checks. A perfectly downloaded bundle can still be wrong for a device if it targets a different native runtime.
Download size and bandwidth
Some systems deliver a complete JavaScript bundle and asset set for every update. Others support patches or asset-level deltas.
Patch delivery can reduce download size when a change is small, but it introduces another failure path. Ask whether the client reconstructs and verifies the complete target state and whether it can fall back to a full update when a patch is missing, unsupported, or invalid.
Operational visibility
At minimum, teams need to identify which release is running when an error occurs. Useful signals include bundle identifiers or hashes, adoption, download and install failures, rollout state, and source maps tied to the exact update.
OTA tooling should complement your crash reporter, not hide update identity from it.
The main approaches available
There is no universal winner. The right choice depends on your application model, operational appetite, existing tooling, and risk tolerance.
Expo Updates and EAS Update
The names are easy to blur together, so it helps to separate them:
-
expo-updatesis the client library that loads compatible updates. - EAS Update is Expo's hosted cloud service for publishing and serving those updates.
- EAS Build is a separate service that produces app binaries.
EAS Update supports Expo projects using Continuous Native Generation and existing React Native projects that install expo-updates. It uses runtime versions for native compatibility, supports staged rollouts, provides deployment insights, and lets teams republish a previous stable update.
For teams already standardized on Expo tooling, that integration can be a strong advantage. Evaluate the workflow conventions, update behavior, and pricing model against your needs rather than treating "uses React Native" as the only requirement.
Other managed OTA providers
A managed provider can reduce the amount of infrastructure your team owns. The tradeoff is that you are adopting the provider's runtime model, release controls, pricing dimensions, and migration path.
Compare providers on concrete behavior rather than broad feature labels. "Rollback," for example, might mean republishing an older release, switching a server-side pointer, or performing automatic recovery on the device. Those are not operationally identical.
Self-hosted or custom infrastructure
Microsoft's App Center retirement guidance points CodePush users to a self-hostable server. Expo also documents self-hosting for the updates protocol. A custom system can offer maximum control over data, cost, and release policy.
That control comes with ownership. Your team becomes responsible for artifact storage, signing, availability, client compatibility, rollout state, observability, abuse prevention, and incident response. Running an update server is not the same as safely operating an OTA lifecycle.
Self-hosting is a reasonable choice when the constraints justify it and the team is prepared to maintain it.
Store-only releases
Choosing not to use OTA is also valid.
Store-only releases keep the delivery model simple and ensure every code change passes through the normal binary pipeline. The cost is slower delivery for small fixes and less control over when users install them.
For apps with infrequent releases, strict review requirements, or a small operational team, that tradeoff may be appropriate.
Questions to ask before choosing
A useful evaluation should be specific enough that two engineers can reach the same answer from the documentation or a test.
- Does it support our actual project shape: Expo, Expo Prebuild/CNG, EAS Build, or bare React Native?
- How is native compatibility represented, and who is responsible for changing the runtime version?
- Does every update download a full bundle, or can the client fetch a smaller patch when only part of the application changes?
- If patching fails, is there a verified full-bundle fallback?
- Are percentage rollouts available? Can targeting rules use application-defined user properties?
- What happens if an update repeatedly crashes before startup completes?
- Can the device recover without successfully running the new JavaScript?
- Are channels, promotions, and rollback operations auditable?
- Can source maps and release identifiers be connected to the existing error tracker?
- Is pricing tied to monthly active users, bandwidth, storage, builds, seats, or some combination?
- Can artifacts and release history be exported if the team later migrates away?
- Which changes still require a store build?
A small failure drill is often more revealing than another feature checklist: publish a deliberately broken update to a test channel, then observe how rollout, detection, rollback, and device recovery actually behave.
Why I built Bundle Drop
Disclosure: I am the creator of Bundle Drop.
I built Bundle Drop because I wanted another managed option with an explicit compatibility boundary and a recovery model that remains useful when an update fails early.
Its delivery model prepares a complete verified bundle and, when possible, a smaller patch. A device can download the patch when only part of the application changed instead of downloading the full update every time. The client reconstructs and verifies the complete target state before activation. If patch transport is unavailable or fails, it falls back to the signed full bundle.
Bundle Drop also provides channels, staged rollouts, and targeting based on application-defined user properties. On-device health checks can return a failing update to the previous verified bundle, or to the binary's embedded bundle when there is no earlier OTA version.
For Expo, the integration is designed for Prebuild/CNG projects and normal EAS Build workflows. EAS Build still creates the application binary; Bundle Drop handles the OTA layer after that binary is deployed. The same runtime-version model also supports bare React Native.
The service's plans are not priced by monthly active users. That was an intentional choice because I wanted teams to evaluate rollout and storage needs without treating user growth itself as an OTA cost.
The documentation describes the runtime and release model, and the Expo integration guide covers config-plugin setup, CNG, EAS Build, and runtime authority. Bundle Drop is one option among several, and I would still recommend testing its failure behavior against your own release requirements before choosing it.
Choose for recovery, not just installation
Installation is the beginning of an OTA system, not the hard part.
Before committing to a provider or a self-hosted design, write down your native compatibility rule, rollout process, health signal, rollback path, observability requirements, and exit strategy. Then test those rules with real release builds.
The best choice is the one your team can understand and operate during an incident—not merely the one with the shortest setup guide.
If you are evaluating the post-CodePush landscape, I would be interested in hearing which operational requirements have mattered most to your team.
Top comments (0)