We’ve all been there. You spend weeks building a feature, testing it on your local simulator or a physical device running a development stream. Everything is butter. No lag, no warnings.
You confidently bundle the app, ship it to TestFlight, wait for Apple to finish processing, download it... and the moment you tap the app icon, it instantly flashes and closes. Hard crash.
Before you lose your mind or start blindly tweaking code, remember that production builds behave entirely differently than development environments.
Here are the four most common reasons your app dies the literal second it hits production, and exactly how to fix them.
1. The Missing Privacy Keys String (Info.plist)
If your app requests permissions for features like Location Services (maps, background location), the Camera, or the Photo Library, Apple requires you to explicitly state why in your Info.plist file using usage description keys (like NSCameraUsageDescription or NSLocationWhenInUseUsageDescription).
The Gotcha
In a local development environment, sometimes a framework will let a missing string slide, fallback to a default, or catch the exception gracefully.
The Production Reality
Apple’s iOS security layer will ruthlessly and instantly terminate the application binary on launch if your production code attempts to initialize a library requiring these permissions without the matching text string configured.
The Fix
Double-check your Info.plist (or your Expo app.json plugins). Make sure every single hardware or permission API your code imports has a clear, user-facing explanation string attached.
2. Missing Push Notification Entitlements
If your application includes code for push notifications (even if you haven’t fully wired up the backend yet), your binary needs specific clearance to launch.
The Gotcha
Local builds often bypass strict entitlement checks, or run using a wildcard development provisioning profile that covers everything loosely.
The Production Reality
When built for distribution, if your app contains code for handling remote notifications but the App Store Provisioning Profile doesn't explicitly have the Push Notifications entitlement enabled, the OS will trigger a fatal launch mismatch exception.
The Fix
Head to your Apple Developer Account under Identifiers, verify that your App ID has Push Notifications checked, and regenerate your production profile.
3. Dead Code Elimination & Aggressive Minification
When building locally, your JS bundling or native compilation keeps debug code, metadata, and helper functions intact.
When you build for production, optimization tools like ProGuard/R8 (for Android native engines) or aggressive tree-shaking strip away "unused" code to shrink the binary size.
The Gotcha
Sometimes, these optimization tools accidentally strip away native modules or reflection classes used by third-party packages, assuming they are dead code because they aren't explicitly referenced in the main thread.
The Production Reality
The app boots up, looks for a compiled native library or native method bridge, finds a missing reference, and triggers a fatal crash right during initialization.
The Fix
If you are using native dependencies, make sure your obfuscation/minify configuration files explicitly include rules to keep specific third-party library paths intact.
4. Broken Initialization Flow (Environment Variables)
How does your app determine its backend URL or third-party service tokens?
If you are relying on a local .env file that is git-ignored, those values might not be making it into your production build machine or CI/CD pipeline.
The Gotcha
The app builds successfully because the compiler doesn't care if a string variable is blank or null at build time.
The Production Reality
On launch, your application's root mounting sequence tries to parse an undefined API key or a null base URL during setup. If your code doesn't have a fallback check, it throws a fatal JavaScript or runtime error before the first screen even renders.
The Fix
Always verify that your build dashboard (like Expo Application Services, GitHub Actions, or local production scripts) has your production environment variables explicitly mapped before hitting compile.
How to Stop Guessing and Find the Proof
Stop guessing and changing random lines of code hoping for a miracle. Apple leaves an exact paper trail for immediate launch crashes.
On Your Test Device
Open up the TestFlight app on your iPhone, tap on the application name, scroll down to Crash Logs, and you can view or share the exact file.
Inside Xcode
Navigate to:
Xcode > Window > Organizer > Crashes
Apple aggregates logs directly from TestFlight users here, often highlighting the exact line of compiled code that triggered the crash (look for terms like SIGABRT or EXC_CRASH).
What's the absolute strangest "works locally, breaks in production" bug you've ever had to hunt down?
Let's talk in the comments!
Top comments (0)