Shipping React Native Apps: Fastlane, CodePush, and the App Store Grind
The gap between "it runs" and "it's released"
A React Native app running in Expo Go on your development machine is maybe 60% of the work. The remaining 40% — code signing, provisioning profiles, store metadata, screenshot generation per device size, staged rollouts, and the actual review process on both stores — is where release day either goes smoothly or turns into an afternoon of certificate errors. Automating this pipeline properly is what turns "ship a mobile app" from an occasional, dreaded event into a routine one.
Fastlane as the automation backbone
# Fastfile — one command, both platforms
lane :release do
increment_build_number
build_app(scheme: "MyApp")
upload_to_app_store(skip_screenshots: true, submit_for_review: true)
build_android_app
upload_to_play_store(track: "production")
end
Fastlane's real value isn't any single step — it's that the entire release process becomes one reproducible command instead of a checklist of manual steps in Xcode and Android Studio that a tired engineer will eventually get wrong at 11pm before a deadline. Certificate and provisioning profile management (via match) in particular removed an entire category of "works on my machine but not in CI" failures that used to eat a half day per release.
CodePush: shipping without waiting on Apple
App Store review can take anywhere from a few hours to several days. For anything that isn't a native code change — copy fixes, a broken API endpoint reference, a UI bug in JS-only code — CodePush pushes an update directly to installed apps, bypassing store review entirely. This is the difference between "we found a bug, we'll fix it in the next release cycle" and "we found a bug, it's fixed for users within the hour."
codePush.sync({
updateDialog: false,
installMode: codePush.InstallMode.ON_NEXT_RESUME,
});
The discipline that matters here is knowing what CodePush can and can't ship — any change touching native modules, permissions, or anything Apple's review guidelines consider a "significant" change still needs a full store release. Using CodePush for those anyway is a fast way to get an app pulled from the store for violating review terms.
Staged rollouts as a safety net
Both stores support percentage-based rollouts — release to 10% of users, watch crash reporting and key metrics for a day, then ramp to 50% and 100% if nothing's on fire. This caught a genuinely bad release once: a crash on a specific older Android OS version that hadn't shown up in any of our test devices, caught at the 10% stage instead of the full user base, because the crash rate spiked visibly in the rollout dashboard before most users ever saw it.
What actually made five apps sustainable
- One shared Fastlane configuration pattern reused across apps, not reinvented per project
- CodePush for the fast-follow fixes that don't need App Store review
- Staged rollouts as a default, not an occasional precaution
- Crash reporting (Sentry) wired in from the first release, not added after the first production crash
None of this is glamorous work, but it's the difference between a mobile release being a routine Tuesday afternoon task and an all-hands-on-deck event every single time.