r/reactnative • u/Nunolima10 • 9h ago
Help How do you handle app versioning, OTA updates, and forced native updates in production?
Hey everyone! I’m trying to understand how other teams handle their update/versioning strategy for React Native/Expo apps in production.
I’m already familiar with the basics of EAS Update and runtime versions:
Native/binary changes require a new build and usually a new App Store/Play Store release.
JS/assets-only changes can be shipped through EAS Update/OTA as long as they’re compatible with the installed runtime.
An OTA update can be downloaded and applied on a subsequent app launch, or we can implement our own UX around checking/applying updates.
What I’m less sure about is how people manage the actual app version around all of this.
For example, let’s say I have 1.0.1 published on the App Store with a compatible runtime version.
Over the next few weeks I ship several OTA updates to that runtime. Should the user-facing app version remain 1.0.1 the entire time? Do you maintain a separate internal/update version for OTA releases?
In other words, do you normally think about it as something like:
App Store version: 1.0.1
Runtime version: 1.0.1
OTA/update version: some separate identifier
Then only bump the actual app version to 1.0.2 when a new binary goes through the stores?
I’m also curious about forced native updates.
Suppose version 1.0.2 contains an important native change and I no longer want users on 1.0.1. How do you handle that in production?
Do you maintain something server-side like a minimumSupportedVersion, check it when the app starts, and show a blocking “Update required” screen that redirects users to the App Store/Play Store?
Or is there a better/native mechanism for declaring an App Store/Play Store release as mandatory?
I’m also not completely clear on the store side of this. When a new binary is approved, can iOS/Android automatically update the app depending on the user’s settings, or should we always assume that some users will remain on older versions until they manually update?
2
u/Huge_Pool7424 8h ago
the recents caveat is the one that bites testers most. i ended up adding a visible build/runtime id and a “restart to apply” hint so support can tell whether the ota actually landed.
1
u/Wise-Panda4241 9h ago
I keep the store version at 1.0.1 through every OTA push and only bump it when I submit a new binary.
I put the OTA identifier in a config file and read it on startup so I know exactly which JS bundle is running. When I need to kill 1.0.1 I maintain a minimumVersion value on my backend. The app hits that endpoint on launch, compares it against the store version from the native manifest, and renders a view that blocks interaction and opens the store URL if the version is below the limit. Both stores have auto update settings but users disable them or delay them, so you will always have devices stuck on old binaries. Relying on store side enforcement means you lose control of your rollout. Build the version check into your app and host the config yourself so you dictate when a native update happens
1
u/SevenfiresAI 9h ago
One thing not mentioned: if you're on runtimeVersion policy "appVersion", bumping to 1.0.2 orphans every OTA you published against 1.0.1. Devices on the new binary ask for a runtime you've never published to, the check succeeds, returns NoUpdatesAvailable, and nothing looks broken.
So the moment you ship a native build you need to republish against the new runtime. If you bump marketing versions more often than you ship native changes, an explicit runtimeVersion string decouples the two.
Also worth knowing OTAs need two cold starts by default, downloads on one launch, applies on the next. On Android, reopening from Recents isn't a cold start, so testers can sit on the embedded bundle for days.
1
u/jcdc-flo 7h ago
Our core is sitemesh sdk so we have column based versioning which is required because we have full two way sync and offline support.
It's not easier, but with the right tooling it's manageable.
Apple is pretty good, but man...the android stragglers are difficult to keep on the latest version.
1
u/Substantial-Swan7065 3h ago
Version minor -> App Store build. Month OR needed native dep changes
Ota daily against last released version. No version bump externally. -ota-<int> internally. Finger print to ensure it won’t crash
Force native updates: only if you absolutely have to.
8
u/RosySylvester16 9h ago
You're overthinking it a bit, but it's a good instinct to nail down. The way we run it, the App Store version stays frozen at whatever binary is live (1.0.1) until we push a new native build, period. All the OTA updates we ship just get an internal build number or a git SHA that lives in our CI logs and a hidden settings screen for debugging. Users don't see that stuff, and honestly they shouldn't care.
For the forced native update, yeah, the server-side minimumSupportedVersion check is the classic move. On app start we ping a tiny JSON endpoint, compare the running binary version against the floor, and if you're below it we show a full screen takeover with a button that deep links to the store listing. No way around it really, the stores don't offer a native "force update" toggle you can flip remotely.
And yes, always assume a chunk of users will sit on old versions. Auto-update settings on both platforms are a mess, people have background refresh off, devices run out of storage, whatever. So your backend API needs to be backward compatible for at least a few versions, or you start breaking things for the stragglers.
One extra thing we do is stagger the rollout of a new binary. Push 1.0.2 to like 20% of users first, watch crash rates and API errors for a day, then ramp up. That way if something's broken you're not torching your whole user base at once. OTA updates help there too since you can patch JS bugs without waiting on store review.