## 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 > 99.5%, cold start p50 < 2s, API error rate < 0.5%, payment success rate > 99%).

- 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 (e.g., com.company.appname ) and semver (e.g., 1.7.0 ) with incrementing build numbers per store upload.

- Disable debug-only libraries and logs in release.

- Separate endpoints and keys for dev, staging, and production using app.config.js or .env files per environment.

- 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 via Babel plugin:

 // babel.config.js
plugins: [
 ['transform-remove-console', { exclude: ['error', 'warn'] }]
] 

- Set network security config; avoid cleartext unless required and scoped. Example res/xml/network_security_config.xml :

 <?xml version="1.0" encoding="utf-8"?>
<network-security-config>
 <domain-config cleartextTrafficPermitted="false">
 <domain includeSubdomains="true">api.example.com</domain>
 </domain-config>
</network-security-config> 

- 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 -Os ).

- Enforce ATS (HTTPS) with exceptions only if absolutely required and documented in Info.plist .

- 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, e.g., kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly ).

- 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 (e.g., 10s connect, 30s read).

## Monitoring and Crash Reporting

- Add a crash reporter (native + JS) and performance monitoring (e.g., Sentry, Datadog, Bugsnag).

- 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
 Sentry.captureException(e);
}, true);

setNativeExceptionHandler(errorString => {
 // send native crash info
 Sentry.captureMessage(errorString);
}); 

- Track: app start time (cold/warm), UI freezes (> 16ms frame drops), network failures, payment flow outcomes, and screen transition timings.

- Alerts: set thresholds for crash-free sessions ( 5% in 5 min), payment drop-offs (> 2% deviation).

## Performance and Startup

- Use Hermes for faster startup and lower memory (typically 20-30% faster TTI on low-end devices).

- Enable inlineRequires and lazy-load heavy screens and modules.

- Avoid expensive work on startup; defer analytics and non-critical fetches until after first paint.

- Optimize images: WebP/AVIF where supported, correct sizes, caching via cache: 'force-cache' headers.

- 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 });

const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const shouldRetry = (error) => !error.response || error.response.status >= 500;

api.interceptors.response.use(undefined, async (error) => {
 const cfg = error.config;
 cfg.__retryCount = cfg.__retryCount || 0;
 if (cfg.__retryCount < 3 && shouldRetry(error)) {
 cfg.__retryCount++;
 const backoff = Math.min(1000 * 2 ** cfg.__retryCount, 8000);
 const jitter = Math.random() * 500;
 await delay(backoff + jitter);
 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 (v6+).

- Handle purchase states: Purchased , Pending , and Unspecified . Show UI for pending (e.g., cash-based payments in some regions like OXXO in Mexico or Boleto in Brazil).

- Always acknowledge purchases within 3 days (consumables) or before granting entitlement (subscriptions).

- Verify purchase tokens server-side against Play Developer API.

- Support subscription upgrades/downgrades and proration modes ( IMMEDIATE_WITH_TIME_PRORATION , etc.).

- 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 within Play Console.

- iOS

- Use TestFlight for internal and external testers (up to 10k external).

- Consider phased release for production (1-day, 7-day, 30-day options).

- Provide compliance metadata and accurate permission descriptions in App Store Connect.

- Versioning and metadata

- Use semantic versioning for marketing version and increment build numbers per upload.

- 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 (e.g., shared_prefs , databases , files/keys ).

- iOS backups

- Mark files that should not be backed up with the NSURLIsExcludedFromBackupKey attribute when appropriate.

- Keychain items may migrate across device restores; plan token invalidation accordingly (e.g., bind tokens to device ID or rotate on first launch after restore).

- Backend coordination: ensure server-side backups, retention, and restore drills align with client expectations.

## Maintenance and SLOs

- Define targets

- Crash-free sessions: > 99.5% (Google Play "Excellent" threshold).

- Cold start p50 99%.

- Reviews

- Weekly monitoring review: crashes, ANRs, perf regressions.

- Monthly dependency review: security advisories and compatibility (React Native, Gradle, Xcode, key libraries).

- Access and keys

- Rotate credentials and cleanup unused access quarterly.

## Upgrades and OS Compatibility

- Plan quarterly React Native and library upgrades; avoid large version jumps (e.g., 0.72 -> 0.74, not 0.70 -> 0.74).

- Track Android Gradle Plugin, Kotlin, Java, Xcode, Swift, and iOS/Android SDK targets in a VERSIONS.md file.

- Test on the latest OS betas ahead of public release; fix deprecations early (e.g., Android 14 foreground service types, iOS 17 privacy manifests).

- Pin toolchain versions in gradle.properties and Package.swift / xcodeproj to reduce unexpected build breaks.

## Incident Response and Rollbacks

- Feature flags and remote kill switches (e.g., LaunchDarkly, Firebase Remote Config) to disable risky features without store updates.

- OTA rollback strategy (where allowed) for JS-only fixes via Expo Updates or CodePush.

- 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 (e.g., "if crash-free sessions alert on-call -> check Sentry -> if new release -> halt rollout\"}).

## 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 (e.g., Pixel 6a and Pixel 8) and compare before/after.

- Trigger a handled JS error to confirm reporting appears in dashboard within 60 seconds.

- Simulate network failures (airplane mode, throttling) 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 > 10%, 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.