Ship React Native Updates the Same Day You Write Them
EAS Update ships JS fixes over the air in minutes while runtime versions keep native changes store-compliant. A same-day mobile pipeline.
A customer reports a crash in your mobile app on a Tuesday. You find the bug in twenty minutes. It is a one-line fix in your JavaScript. Then you remember it is a mobile app, and your stomach drops, because shipping that fix means a new build, a new submission, and a wait in the App Store review queue that could stretch from hours to days while the crash keeps happening to real users.
That used to be the cost of mobile. It is not anymore. With Expo's EAS pipeline, a JavaScript fix can reach your users in minutes, without an app store review, because the part of your app that changed is not native code. The trick is understanding which changes can fly over the air and which still need a full binary, and building a pipeline that handles both correctly so you never brick production trying to move fast. That review queue is also why an app store rejection costs a shipping startup so much, and why minimizing trips through it matters.
The three tools, and what each one does
Expo Application Services (EAS) splits the job into three distinct services, and the whole strategy depends on keeping them straight:
- EAS Build compiles your iOS and Android binaries in the cloud. This is the heavy, native step. It produces the
.ipaand.aabthat the stores actually run. - EAS Submit uploads those binaries to App Store Connect and Google Play. It automates the submission you used to do by hand.
- EAS Update ships over-the-air JavaScript bundles. It updates the non-native parts of your app, the JS, the styling, the images, without a new binary and without store review.
That third one is what changes your week. Your React Native app is mostly JavaScript running on top of a native shell. When you fix a bug in the JS, the native shell has not changed. EAS Update lets you push the new JS bundle straight to installed apps, and they pick it up on next launch. The Tuesday crash from the opening gets fixed and deployed before lunch.
The rule that keeps you out of trouble: runtime versions
The single most important concept in this pipeline is the runtime version, because it is what stops you from pushing a JS bundle to a binary that cannot run it.
Every binary EAS Build produces is stamped with a runtimeVersion. Think of it as a contract burned into the app at build time that says "I am compatible with JS bundles tagged with exactly this runtime." When you publish an OTA update, it is also tagged with a runtime version. A binary only accepts a bundle whose runtime version matches its own. If they differ, the binary refuses the bundle.
This matters because your JavaScript often depends on native code. If a JS update calls a native module that only exists in a newer binary, pushing that JS to an older binary would crash it. The runtime version is the guardrail. You bump the runtime version whenever you change native code (add a native dependency, change app config that affects the native layer, upgrade the Expo SDK), and you keep it stable when you only change JavaScript. Then OTA updates flow safely to every compatible binary and are automatically withheld from incompatible ones.
A common configuration uses a policy so you do not manage this by hand:
{
"runtimeVersion": { "policy": "appVersion" }
}
With the appVersion policy, the runtime version tracks your app version, so a binary built for version 2.1.0 only accepts updates intended for 2.1.0. The right policy depends on how you version, but the principle is constant: tie the runtime to your native compatibility, and OTA stops being dangerous.
What you can ship over the air, and what you cannot
This is where teams get burned, so be precise. EAS Update can change the JavaScript, styles, and image assets of your app. It cannot change native code. And crucially, the app stores have rules about OTA updates that you must follow, because Expo gives you the mechanism but not permission to abuse it.
The boundaries that matter:
- You cannot swap native code over the air. Anything that requires recompiling the native binary, a new native module, a permission change, an SDK upgrade, needs a full EAS Build and a store submission.
- You cannot materially change the app's purpose or behavior through an OTA update. Apple's review guidelines, including guideline 3.3.1, exist to stop apps from becoming something entirely different after review approved them. OTA updates are for fixing and improving the app the stores approved, not for shipping a secretly different app.
- Bug fixes, copy changes, layout tweaks, and feature flags are fair game, because they are exactly the kind of non-native improvement the mechanism was built for. A push-notification deep link that lands users on the wrong screen, for instance, is a JS-only fix you can ship over the air once you have made deep links and attribution survive the app store round trip.
Stay inside those lines and OTA is a legitimate, store-compliant tool. Cross them and you risk your app's standing in the store, which is a far worse problem than a slow review.
A pipeline that moves fast without breaking production
Speed without safety is how you push a bad bundle to your entire user base at once. The playbook that ships same-day fixes without bricking production looks like this:
- Channels per environment. Point preview builds at a
previewchannel and production builds at aproductionchannel. An update published topreviewreaches only internal builds, so you test the actual OTA delivery before any customer sees it. - Staged rollouts. Do not push a new bundle to 100 percent of users instantly. Roll it out to a small percentage first, watch your crash and error monitoring, then ramp to full. If the bundle is bad, only a fraction of users are affected and you have time to react.
- A rollback you can trigger in seconds. Because OTA delivery is just pointing the channel at a bundle, rolling back is republishing the last known-good bundle to the channel. Keep that path tested and one command away, so a bad push is a two-minute recovery rather than a fire drill, the same instinct behind a deploy script that rolls itself back when health checks fail. The staged rollout itself is just rolling out risky features behind flags you can kill in one click applied to the whole bundle.
- CI automation. Wire
eas build,eas submit, andeas updateinto your deploy flow so the JS-only fix is a single command that publishes to the right channel, and the native release is a single command that builds and submits. The pipeline, not your memory, decides which path a change takes.
The mental model that keeps it all coherent: most changes are JavaScript and go out over the air in minutes; native changes are rarer, go through Build and Submit, and bump the runtime version. When you treat those as two clearly separate tracks, you get the speed of web deploys for the majority of your work and the discipline of native releases for the minority that needs it.
The outcome that matters
The point of this pipeline is not the tooling. It is that a fix you write at 10am can be on every user's phone by 11am, the same day, without begging a review queue. The crash you found in twenty minutes stops happening to customers before the day is out. Your mobile release cadence starts to feel like your web release cadence, which is the thing every founder wants from a mobile team and rarely gets.
The same zero-downtime instinct we hold on the server side, where shipping Next.js updates with zero downtime using PM2 keeps web releases invisible to users, carries straight over to mobile here. That speed is what we build into the mobile apps we ship, with the runtime-version discipline and rollback safety in place from day one, so moving fast never means rolling the dice on production. If you are still deciding the shape of the app itself, it is worth weighing when a PWA beats a native app for your budget before you commit to this pipeline at all. The difference between a mobile team that ships a fix in an hour and one that ships it in a week is not talent. It is whether the pipeline was built to let JavaScript fly while keeping native changes honest. Build it once, and the Tuesday crash stops being a multi-day ordeal.






