Staged rollouts and instant rollbacks: a safe OTA release playbook
Ship to 5% first, watch, then expand — or pull it back in seconds. A practical rollout discipline for React Native over-the-air updates, and exactly when to abort.
Over-the-air updates remove the days of latency that app review used to impose. That is the whole point — but review was also an accidental safety net. When you can ship to every device in minutes, you can also ship a bug to every device in minutes. Staged rollouts are the discipline that replaces review as your guardrail.
The pattern
Expand in steps, with a soak window at each one: 5% → 25% → 50% → 100%. At each step you are answering a single question — is this release healthier than or equal to the one it replaces? Only move up when the answer stays yes for the full soak window.
# Start narrow
npx bettercodepush deploy --platform ios --target-app-version 1.4.0 \
--channel production --rollout 5
# The deploy output prints the release ID. Soak, watch metrics, then widen.
RELEASE_ID="paste-the-release-id"
npx bettercodepush bundle update "$RELEASE_ID" \
--rollout-cohort-count 250 --yesWhat to watch during the soak
- Crash-free session rate, compared against the previous release, not an absolute number.
- JavaScript error rate and unhandled promise rejections.
- Your one or two most important product funnels (sign-in, checkout, whatever pays).
- Update-apply success: are devices on the rollout actually landing on the new bundle?
Decide your abort criteria before you deploy
The worst time to define "bad" is while you are staring at a graph at 11pm. Write the thresholds down before the release: for example, abort if the crash-free rate drops more than half a point below the prior release, or if the checkout funnel falls by more than a few percent. Pre-committing turns a judgment call into a checklist.
Rollback mechanics
There are two distinct recovery paths, and it helps to keep them straight:
- Server-side disable. You disable the rollout; the next update check on each device falls back to the last known-good release. History is preserved — nothing is deleted — so you keep a full audit trail of what shipped and when.
- Client-side auto-fallback. If a new bundle crashes on launch before it is marked good, the client reverts to the previous working bundle on its own, so a bad release cannot brick the app into a crash loop.
Operational habits that pay off
- Keep a separate channel per environment (staging, production) and always deploy to staging first.
- Always target a specific app version so a JS bundle never lands on a native build that cannot run it.
- Keep a known-good release identified so rollback is a decision, not an investigation.
- Do not roll out to 100% right before a weekend or a holiday.
If you are still setting this up, the mechanics slot straight into the migration workflow: deploy narrow, inspect the active release, expand or disable.