Intro
Production is a different game than development. This checklist gives you a practical, field-tested path to harden a React Native app for release, secure data and payments, add monitoring, improve performance, plan releases, and recover from incidents across Android, iOS, and Expo.
Workflow Overview
Use a simple, explicit flow to reduce rework and surprises:
- Plan: define target devices, OS versions, and success metrics (crash-free sessions, cold start p50, error rate, payment success rate).
- Configure: set release flags (Hermes, minification, resource shrinking, inline requires), versioning, and environment separation.
- Secure: protect secrets, lock network transport, and review permissions.
- Instrument: crash reporting, JS error handlers, performance and business metrics.
- Test: smoke tests on low-end and high-end devices; payment sandbox; offline and flaky network.
- Release gradually: internal testers, staged rollouts, watch dashboards.
- Operate: monitor, on-call ownership, backups, and regular upgrades.
Configuration and Build Flags
Harden release builds on both platforms.
- General
- Set proper app ID/bundle ID and semver (e.g., 1.7.0) and build numbers.
- Disable debug-only libraries and logs in release.
- Separate endpoints and keys for dev, staging, and production.
- Metro config: speed up startup by deferring requires
// metro.config.js
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: { inlineRequires: true }
})
}
};
- Android (Gradle)
- Enable Hermes and R8, shrink resources, split ABIs to reduce APK size.
// android/app/build.gradle
project.ext.react = [ enableHermes: true ]
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
splits {
abi { enable true; reset(); include 'armeabi-v7a','arm64-v8a','x86_64'; universalApk false }
}
}
- Strip logs in release (e.g., replace console.* in Babel for release builds or guard logs).
- Set network security config; avoid cleartext unless required and scoped.
- Configure Play App Signing and secure keystores.
- iOS (Xcode)
- Enable Hermes in Podfile if using Hermes, set Release flags (dead code stripping, bitcode off for current toolchains, optimization level).
- Enforce ATS (HTTPS) with exceptions only if absolutely required and documented.
- Configure correct provisioning and signing for Release.
Secrets and Security
- Never embed long-lived secrets in the app. Keep private keys on the server.
- Store access tokens in platform secure storage:
- iOS: Keychain (restrict access group and accessibility level as needed).
- Android: EncryptedSharedPreferences or Keystore-backed storage.
- Avoid storing secrets in AsyncStorage.
- Payments
- Stripe: keep publishable key in app, but create ephemeral keys server-side. Do not store customer secrets on-device.
- Google Play Billing: validate purchases on a secure server; do not trust client-only checks.
- Permissions: request only what you need; provide in-app rationale; handle denial gracefully.
- Transport: enforce HTTPS/TLS, pin domains if you control the backend; set sane timeouts.
Monitoring and Crash Reporting
- Add a crash reporter (native + JS) and performance monitoring.
- Initialize early in app startup and include release, build number, device, and user ID or session ID (respect privacy and consent).
- Capture unhandled JS errors and rejections:
import { setJSExceptionHandler, setNativeExceptionHandler } from 'react-native-exception-handler';
setJSExceptionHandler((e, isFatal) => {
// send to your crash service
}, true);
setNativeExceptionHandler(errorString => {
// send native crash info
});
- Track: app start time (cold/warm), UI freezes, network failures, payment flow outcomes, and screen transition timings.
- Alerts: set thresholds for crash-free sessions, error spikes, payment drop-offs.
Performance and Startup
- Use Hermes for faster startup and lower memory.
- Enable inlineRequires and lazy-load heavy screens and modules.
- Avoid expensive work on startup; defer analytics and non-critical fetches.
- Optimize images: webp/avif where supported, correct sizes, caching.
- Use FlatList/SectionList with keyExtractor, getItemLayout when possible; avoid re-render storms (memoize props, useCallback/useMemo appropriately).
Networking and API Resilience
- Set timeouts and retries with exponential backoff and jitter.
import axios from 'axios';
const api = axios.create({ baseURL: 'https://api.example.com', timeout: 10000 });
api.interceptors.response.use(undefined, async error => {
const cfg = error.config;
cfg.__retryCount = cfg.__retryCount || 0;
if (cfg.__retryCount < 3 && shouldRetry(error)) {
cfg.__retryCount++;
await delay(Math.min(1000 * 2 ** cfg.__retryCount, 8000));
return api(cfg);
}
return Promise.reject(error);
});
- Use idempotency keys for write operations (especially for payments).
- Offline support: cache reads, queue writes, and reconcile on connectivity restore (use NetInfo to detect status).
- Validate SSL and enforce ATS on iOS. Avoid cleartext traffic; if unavoidable, scope hosts and paths tightly.
Payments: Play Billing and Stripe
- Google Play Billing (in-app purchases or subscriptions)
- Use the latest BillingClient.
- Handle purchase states: Purchased, Pending, and Unspecified. Show UI for pending (e.g., cash-based payments in some regions).
- Always acknowledge purchases within the required window.
- Verify purchase tokens server-side against Play Developer API.
- Support subscription upgrades/downgrades and proration.
- Test with license test accounts and sandbox products.
- Stripe (cards, wallets)
- Prefer PaymentSheet for a secure, standardized flow.
- Generate ephemeral keys on your server; never embed secret keys in the app.
- Handle 3DS authentication, app switching, and Activity/UIViewController lifecycle.
- Use idempotency keys for payment intents to avoid duplicates on retries.
- Log payment attempt outcomes and correlate with backend logs.
Expo Considerations
- Choose managed vs bare based on native needs.
- EAS Build
- Define build profiles for development, preview, and production.
- Use runtime versions and channels for safe update segmentation.
- Updates
- Use OTA updates for small JS/asset fixes that comply with store policies.
- Any native module or permission changes require a new store build.
- Config
- Keep app.json/app.config in sync with store metadata (icons, permissions, deep links, schemes).
Release Management: Android and iOS
- Android
- Use internal testing track for smoke tests, closed testing for broader QA, then staged rollout to production (e.g., 5%, 25%, 50%, 100%).
- Enable Play App Signing and integrity protections.
- Keep a rollback plan: halt rollout or roll back to a prior release.
- iOS
- Use TestFlight for internal and external testers.
- Consider phased release for production.
- Provide compliance metadata and accurate permission descriptions.
- Versioning and metadata
- Use semantic versioning for marketing version and increment build numbers.
- Maintain clear changelogs and update notes.
Data, Storage, and Backups
- Classify data: credentials/tokens, user content, cached API data, analytics.
- Store sensitive data in secure storage; never in plaintext files or AsyncStorage.
- Migrations: version your schema for SQLite or key-value stores and test upgrades and downgrades.
- Android backups
- Control Auto Backup with a fullBackupContent XML to exclude sensitive paths.
- iOS backups
- Mark files that should not be backed up with the do-not-backup attribute when appropriate.
- Keychain items may migrate across device restores; plan token invalidation accordingly.
- Backend coordination: ensure server-side backups, retention, and restore drills align with client expectations.
Maintenance and SLOs
- Define targets
- Crash-free sessions: e.g., > 99.5%.
- Cold start p50/p90 thresholds.
- API error rate, payment success rate.
- Reviews
- Weekly monitoring review: crashes, ANRs, perf.
- Monthly dependency review: security and compatibility.
- Access and keys
- Rotate credentials and cleanup unused access regularly.
Upgrades and OS Compatibility
- Plan quarterly React Native and library upgrades; avoid large version jumps.
- Track Android Gradle Plugin, Kotlin, Java, Xcode, Swift, and iOS/Android SDK targets.
- Test on the latest OS betas ahead of public release; fix deprecations early.
- Pin toolchain versions to reduce unexpected build breaks.
Incident Response and Rollbacks
- Feature flags and remote kill switches to disable risky features without store updates.
- OTA rollback strategy (where allowed) for JS-only fixes.
- Store controls
- Android: halt or roll back staged rollouts in Play Console.
- iOS: remove from sale or submit expedited review for hotfixes when necessary.
- Runbooks
- Define steps for payment outage, API outage, and crash spike events.
- Include contact points, log links, dashboards, and decision trees.
Local Pilot Plan
Start small so you can verify quickly and safely.
Scope
- Enable Hermes and inlineRequires.
- Add crash reporting and capture unhandled JS errors.
- Wrap one critical API call with timeouts and backoff, plus idempotency for writes.
Verification
- Measure cold start time on two devices and compare before/after.
- Trigger a handled JS error to confirm reporting.
- Simulate network failures and confirm retries and user messaging.
Rollout
- Ship to an internal test track (Android) or TestFlight (iOS) with 10-20 testers.
- Success criteria: no new crashes from pilot changes, p50 cold start improved, and API error handling verified.
Conclusion
A reliable release comes from repeatable operations: hardened builds, secured secrets, robust monitoring, tuned performance, resilient networking, careful payments handling, and disciplined releases. Start with a narrow pilot, verify improvements, then expand coverage to the rest of the app. Keep upgrades and maintenance on a predictable cadence, and practice rollback paths before you need them.