react-brain
Browse decisions
React & language foundations 3App architecture 9UI 12Platform & native 9Build, test, observe, secure 6AI in React apps 3stack composerdecision recordscensusstaleness bencharchitecturechangelogmethodologyroadmap

entriestooling-ops · verified 2026-07-13 · react-native

Over-the-air (OTA) JS updates & release channels (React Native)

reviewedconfidence: mediumthis tier holds 50% on the public scorecard (3/19 graded · 0 overturned) →

related decisions: rn-versions · brownfield

cited by: brownfield

re-verified 3× — 2026-07-13 · 2026-07-10 · 2026-07-09 · changelog

recommendation

On Expo/EAS → EAS Update via the expo-updates client (channels + phased rollouts + rollback, and diffing makes updates dramatically smaller since SDK 55/56). Hard self-host requirement → hot-updater, or the open expo-updates protocol with your own server. Anything still pointed at the retired CodePush service is silently broken — migrate.

  • Expo app → EAS Update (first-party; channels/rollouts/rollback built in)
  • self-hosted / no-vendor requirement → hot-updater, or expo-updates protocol + your own server
  • legacy CodePush app → standalone code-push-server keeps it alive while you migrate; the hosted service is gone
  • the change touches native code → OTA cannot ship it; that's a store release (see RB-E-RN-VERSIONS)
  • RN embedded in a native app (brownfield) → Expo Updates works in isolated brownfield since SDK 55 (see RB-E-BROWNFIELD)

Options & tradeoffs

the field considered — and why each one isn’t the default here

optiontradeoffevidence
EAS Update (expo-updates client)the hosted default — channels, phased rollouts, republish-to-rollback; SDK 55 added bsdiff bytecode/bundle diffing (est. ~75% smaller update downloads; default in SDK 56)2.7M/wk · ships in 3/14
expo-updates + custom serverthe Expo Updates protocol is open — self-host the server, keep the battle-tested client; more ops work than EAS
hot-updaterself-hosted open-source OTA (gronxb) explicitly positioned as the CodePush successor; actively maintained (v0.35.x, 2026-07); younger than expo-updates17k/wk
code-push-server (standalone)Microsoft's open-sourced server, compatible with the legacy react-native-code-push client — a self-host stopgap for existing CodePush apps, not a bet for new ones
react-native-code-push (RETIRED service)App Center + the hosted CodePush service shut down 2025-03-31; the client npm has been dormant since 2024-12 — migrate

evidence: npm weekly downloads (signals snapshot) · “ships in n/D” = adoption across the production-app census, honest denominators

Migration lines

version and deprecation lines this decision tracks — for YOUR repo's sequenced plan: the doctor or npx -y @heart-it/react-brain migrate .

react-native-code-push → hot-updater (self-hosted successor) or expo-updates + EAS Update [dead · effort M]

the hosted CodePush service shut down 2025-03-31 and the client npm has been dormant since 2024-12 — anything still pointed at the service is silently broken; standalone code-push-server is a stopgap, not a destination

github.com/microsoft/react-native-code-push

npm weekly downloads (from the corpus's last signals run): expo-updates 2.7M · react-native-code-push 44k · hot-updater 17k

Verified notes

NEW entry (2026-07-09): OTA delivery was a corpus gap surfaced by Native Weekly's 2026 run (bundle-diffing, runtime channel-switching, and iteration-speed coverage across issues 12–14). DURABLE FACTS, verified: Microsoft retired App Center AND the hosted CodePush service on 2025-03-31 (repo archived; standalone code-push-server open-sourced as the escape hatch); EAS Update gained binary bsdiff patches for Hermes bytecode in SDK 55 — the client applies diffs against previously installed bytecode instead of re-downloading whole bundles, an estimated ~75% download reduction, opt-in in SDK 55 and default in SDK 56 (verified vs the SDK 55 changelog). Store policy allows updating interpreted JS/assets within guidelines; native changes always require a store release. OTA is also the rollback story: republishing a known-good update is the fastest production mitigation RN has.

Canonical reading

Editorial annotations on why each piece matters — the articles themselves are the originals; read them there.

The production playbook for OTA updatesExpo

The operational canon for this entry: staged percentage rollouts as exposure control, what to monitor while an update bakes, and aborts vs republish-rollbacks as distinct mitigations — 'sometimes it helps to go a little slower.' Vendor-authored but the staged-rollout discipline is platform-agnostic. (Body verified via the page's embedded content JSON — the blog is a JS shell.)

EAS Update introduction (docs)Expo

The reference for the hosted-default choice: how updates map to channels, phased rollouts to a percentage of users, and rollback-by-republish ('much like a new commit'). Read before designing a release-channel scheme.

Sources

The full explanation

The reviewed long-form essay behind this entry — the why, not a how-to. Also on GitHub.

About over-the-air (OTA) JS updates & release channels (React Native)

Diataxis: Explanation. This page builds understanding of OTA delivery — the reasoning behind the picks. It is not a setup guide. Store releases and native versioning are owned by RB-E-RN-VERSIONS; brownfield constraints by RB-E-BROWNFIELD; the monitoring an update bake relies on by RB-E-OBSERVABILITY. Read this for the why.

The one principle that organises everything: OTA is your rollback story, not a convenience

The seductive framing of OTA is "ship JS fixes without waiting for the store." The correct framing is the inverse: republishing a known-good update is the fastest production mitigation React Native has, and everything about how you run OTA should serve that. So run updates like releases — channels, staged percentage rollouts as exposure control, a monitored bake period, and abort vs republish-rollback as distinct mitigations learned before you need one. Respect the hard boundary: OTA can update interpreted JS and assets within store guidelines, but a native change can never ship OTA — that is a store release (RB-E-RN-VERSIONS). And know the history: the hosted CodePush service died 2025-03-31, so "still pointed at CodePush" is the silent failure mode of this category.

The default, and why

On Expo/EAS → EAS Update via the expo-updates client (channels + phased rollouts + rollback, and diffing makes updates dramatically smaller since SDK 55/56). Hard self-host requirement → hot-updater, or the open expo-updates protocol with your own server. Anything still pointed at the retired CodePush service is silently broken — migrate.

EAS Update is the hosted default because the release-engineering pieces are built in: updates map to channels, roll out phased to a percentage of users, and roll back by republishing — "much like a new commit." The client got materially better in SDK 55: bsdiff diffing applies patches against previously installed Hermes bytecode instead of re-downloading whole bundles — an estimated ~75% smaller update downloads, opt-in in SDK 55 and default in SDK 56. The self-host escape hatches are real: the Expo Updates protocol is open (bring your own server, keep the battle-tested client), and hot-updater exists precisely as the CodePush successor for no-vendor shops.

The landscape, and when each piece earns its place

EAS Update (expo-updates client) — the hosted default. Channels, phased rollouts, republish-to-rollback; SDK 55 added bsdiff diffing (est. ~75% smaller downloads; default in SDK 56). The first-party pick for any Expo app.

expo-updates + custom server — the protocol is open, so you can self-host the server and keep the battle-tested client. More ops work than EAS; earns its place under a no-vendor rule.

hot-updater — self-hosted open-source OTA (gronxb), explicitly positioned as the CodePush successor. Actively maintained (v0.35.x, 2026-07) but younger than expo-updates — the self-host pick when you don't want the Expo client at all.

code-push-server (standalone) — Microsoft's open-sourced server, compatible with the legacy react-native-code-push client. A self-host stopgap that keeps an existing CodePush app alive while you migrate — not a bet for new ones.

react-native-code-push (RETIRED service) — App Center and the hosted CodePush service shut down 2025-03-31; the repo is archived and the client npm has been dormant since 2024-12. Migrate (urgency: dead) to hot-updater or expo-updates + EAS Update.

Tradeoffs and failure modes to name out loud

  • Still pointed at the dead service. The hosted CodePush shutdown doesn't crash your app — it just stops updating it. Anything still targeting the retired service is silently broken; standalone code-push-server is a stopgap, not a destination.
  • OTA run as a hotfix cannon, not a release. The production playbook is staged percentage rollouts and a monitored bake period — "sometimes it helps to go a little slower." Shipping to 100% instantly forfeits the exposure control that makes OTA safe.
  • Conflating abort with rollback. They are distinct mitigations — halting an in-progress rollout vs republishing a known-good update — and the time to learn the difference is before the incident, not during it.
  • Expecting OTA to carry a native change. Store policy allows updating interpreted JS/assets within guidelines; native changes always require a store release (RB-E-RN-VERSIONS). Every OTA pipeline needs the "does this touch native?" gate.
  • Self-hosting as identity, not requirement. Both self-host paths cost real ops work, and hot-updater is younger than expo-updates. Take them for a hard no-vendor requirement.

How it interacts with the rest of the stack

  • Native versioning (RB-E-RN-VERSIONS). The hard boundary: the moment a change touches native code, OTA is out and the store-release machinery owned there is in — two halves of one release story.
  • Brownfield (RB-E-BROWNFIELD). RN embedded in a native app can still get OTA — Expo Updates works in isolated brownfield since SDK 55; the embedding constraints live there.
  • Observability (RB-E-OBSERVABILITY). A "monitored bake period" is only as good as the monitoring behind it; the tooling that tells you whether to abort or roll back is owned there.

In one paragraph

OTA is your rollback story: republishing a known-good update is the fastest production mitigation React Native has, so run updates like releases — channels, staged percentage rollouts, a monitored bake period, and abort vs republish-rollback as distinct mitigations. Default to EAS Update via the expo-updates client (SDK 55's bsdiff diffing, an estimated ~75% smaller downloads, default in SDK 56); with a hard self-host requirement take hot-updater or the open expo-updates protocol on your own server. Native changes never ship OTA — that's a store release (RB-E-RN-VERSIONS) — and the hosted CodePush service died 2025-03-31, so anything still pointed at it is silently broken and must migrate.


See also: RB-E-RN-VERSIONS (store releases; the native-change boundary), RB-E-BROWNFIELD (Expo Updates in isolated brownfield since SDK 55), RB-E-OBSERVABILITY (the monitoring a bake period depends on).

Related in tooling-ops: build · testing · dx · observability · security