E-NO
React Native upgrade 10 Min Read

React Native Upgrade Playbook: Inventory, Align, Verify, Roll Back

calendar_today Published: 2026-08-09
update Last Updated: 2026-08-12
analytics SEO Efficiency: 97%
Technical guide illustration for React Native Upgrade Playbook: Inventory, Align, Verify, Roll Back.

A step-by-step, version-agnostic React Native upgrade guide with inventory tables, cross-platform alignment commands, verification checks, and rollback procedures.

TL;DR

  • Capture a precise baseline tag and inventory before any change.
  • Upgrade in scoped steps: RN core → iOS template/toolchain → Android template/toolchain → critical libraries.
  • Verify with repeatable Debug-build checks, then graduate to Release builds on physical devices.
  • Keep a fast rollback path via git tags, frozen lockfiles, and clean-native rebuilds.
  • Gate risky changes with feature flags and staged rollouts; use OTA only when native ABI is stable.

Prerequisites & Assumptions

  • Clean git state: working tree committed or stashed; no untracked native artifacts (ios/build, android/.gradle, ios/Pods).
  • CI parity: Node, Yarn/npm, Java, Xcode, CocoaPods, Android SDK/NDK versions match your CI agents exactly.
  • Platform access: Apple Developer account and Google Play Console access for Release validation and staged rollout.
  • Physical devices: Required for Hermes JIT/AOT behavior, billing libraries, and Release performance profiling.
  • Convention: All versions, bundle IDs, scheme names, and paths are placeholders; replace with your project values.

Architecture & Runtime Context

Understanding coupling points prevents silent regressions.

  • Hermes vs JavaScriptCore: Hermes compiles JS to bytecode ahead-of-time (AOT on iOS, JIT+AOT on Android). Mixed Hermes/JSC settings across platforms cause ABI mismatches and crashes. Keep enableHermes / :hermes_enabled identical on iOS and Android.
  • Metro cache: Metro caches module resolution and transforms. Native upgrades (RN version, native modules) invalidate this cache. Always run npx react-native start --reset-cache after native changes.
  • Native toolchain coupling: Each RN version pins minimum Xcode, CocoaPods, AGP, Gradle wrapper, NDK, minSdkVersion, and iOS deployment target. Drifting from the template causes build failures or runtime crashes. Use the upgrade helper to read the exact requirements for your target version.

Version Compatibility Matrix & Upgrade Helper Usage

Consult the official React Native release notes and the upgrade helper for the authoritative matrix. The table schema below shows what to record for your target version X.Y.Z.

ComponentSource of TruthExample Value (Constructed)Notes
React Nativereactnative.dev/versions / Upgrade Helper0.76.xTarget version
XcodeUpgrade Helper diff / Release notes15.4Minimum supported
CocoaPodsRelease notes1.15.2Match team/CI
iOS Deployment Targetios/Podfile template diff15.1platform :ios, '15.1'
AGPandroid/build.gradle template diff8.5.0com.android.application
Gradle Wrappergradle-wrapper.properties template diff8.7distributionUrl
NDKandroid/app/build.gradle template diff26.1.10909125Side-by-side install OK
compileSdkVersionTemplate diff34Google Play requirement
minSdkVersionTemplate diff24Library minimums may raise this
targetSdkVersionTemplate diff34Google Play requirement

Fetch the template diff for your target version:

# Dry-run CLI upgrade (uses rn-diff-purge under the hood)
npx @react-native-community/cli upgrade --dry-run X.Y.Z

# Or review the web upgrade helper (search "React Native upgrade helper" on reactnative.dev)

Review diffs via git diff after running the upgrade command, or inspect the web helper's file-by-file changes. Prefer manual template alignment when your project has heavy customizations (custom MainApplication, AppDelegate, build scripts, or forked native modules).

Inventory

Capture the exact runtime and build state before any change. Record in a ticket or README-upgrade.md.

ItemWhere / CommandExample Value (Constructed)Notes
React Nativepackage.json or node -p "require('./package.json').dependencies['react-native']"0.74.5Current version
Node & Package Managersnode -v, npm -v, yarn -v, pnpm -vNode 20.12, Yarn 4.1Match CI
iOS Toolchainxcodebuild -version, pod --versionXcode 15.3, CocoaPods 1.15.2Team sync
Android Toolchaincat android/gradle/wrapper/gradle-wrapper.properties, grep "com.android.tools.build:gradle" android/build.gradleGradle 8.5, AGP 8.4.0Align with RN template
SDK Levelsandroid/app/build.gradle, ios/PodfileminSdk 23, targetSdk 34, compileSdk 34, iOS 15.1Store policies
Hermesgrep hermes android/app/build.gradle ios/PodfileenabledConsistency required
Critical Librariesnpm ls --depth=0 | grep -E "navigation|reanimated|gesture|stripe|billing"@react-navigation/native 6.x, react-native-reanimated 3.xPeerDependency constraints
CI ParityCI config (.github/workflows, bitrise.yml)Node 20.12, Xcode 15.3, JDK 17Must match local

Expo notes (constructed):

  • Managed workflow: note SDK version (e.g., SDK 51); run npx expo doctor before upgrading.
  • Bare workflow: treat as standard RN app; expo prebuild --clean can validate native config after changes.

Safe Configuration Path

Scope and sequencing keep the blast radius small. Promote changes in observable steps.

1. Choose Target Version & Create Baseline

git checkout -b upgrade/rn-X.Y.Z
git tag pre-rn-X.Y.Z # baseline tag for fast revert

Commit lockfiles (yarn.lock / package-lock.json) if not already tracked. Record the inventory table in the branch description.

2. Define a Narrow Pilot Flow

Select one screen touching JS logic, native UI, and network I/O (constructed example: Settings → Login → Fetch Profile). Define pass/fail checks: no redbox, API 200, token persisted, UI renders.

3. Upgrade React Native Core & Apply Template Diffs

# JavaScript dependency (pick one)
npm install [email protected] --save
# or
yarn add [email protected]

# Apply template diffs via CLI (uses rn-diff-purge)
npx react-native upgrade X.Y.Z

Resolve prompts carefully: prefer keeping project-specific changes. Inspect git diff immediately; commit the RN bump and template alignment separately.

4. Dependency Upgrade Order (PeerDependency-Aware)

Upgrade critical libraries one at a time, committing after each. Read changelogs for minimum RN/Android/iOS requirements.

  1. react-native (core)
  2. @react-native-community/* (cli, netinfo, async-storage, etc.)
  3. Navigation (@react-navigation/*)
  4. react-native-gesture-handler, react-native-reanimated (tight RN coupling)
  5. react-native-stripe-sdk, react-native-google-play-billing (store policy coupling)
  6. Image loaders, analytics, crash reporting, others

iOS Alignment

Align Xcode project, CocoaPods, and signing to the target RN template.

Podfile Configuration (Constructed Example)

# ios/Podfile
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'

platform :ios, '15.1' # from template diff
prepare_react_native_project!

target 'YourApp' do
  config = use_native_modules!
  use_react_native!(
    :path => config[:reactNativePath],
    :hermes_enabled => true, # match Android
    :fabric_enabled => false, # enable when ready
    :flipper_configuration => FlipperConfiguration.disabled, # or .enabled for Debug
    :app_path => "#{Pod::Config.instance.installation_root}/.."
  )
  post_install do |installer|
    react_native_post_install(installer)
    # Workaround for Xcode 15+ signing in CI
    installer.pods_project.targets.each do |target|
      target.build_configurations.each do |config|
        config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
      end
    end
  end
end

Install & Build Debug

cd ios
pod install --repo-update
cd ..

# Debug build for simulator (CI-friendly)
xcodebuild -workspace ios/YourApp.xcworkspace \
  -scheme YourApp \
  -configuration Debug \
  -sdk iphonesimulator \
  -derivedDataPath ios/build \
  clean build

Key checks: Deployment target matches template; hermes_enabled consistent; Flipper disabled for Release or aligned to RN version; signing set to CODE_SIGNING_ALLOWED=NO for CI.

Android Alignment

Align Gradle wrapper, AGP, NDK, SDK levels, and Hermes/Fabric flags.

Gradle Wrapper & AGP (Constructed for RN 0.76 Target)

# Update wrapper
cd android
./gradlew wrapper --gradle-version 8.7
cd ..
// android/build.gradle (project level)
plugins {
  id 'com.android.application' version '8.5.0' apply false
  id 'com.android.library' version '8.5.0' apply false
  id 'org.jetbrains.kotlin.android' version '1.9.20' apply false
}

App Module SDK & Hermes (Constructed)

// android/app/build.gradle
android {
  namespace "com.yourcompany.yourapp"
  compileSdkVersion 34
  ndkVersion "26.1.10909125"

  defaultConfig {
    applicationId "com.yourcompany.yourapp"
    minSdkVersion 24
    targetSdkVersion 34
    versionCode 1
    versionName "1.0"
  }

  // Hermes & Fabric flags (match iOS)
  project.ext.react = [
    enableHermes: true,
    enableFabric: false
  ]
}

ProGuard / R8 Keep Rules (Constructed)

# android/app/proguard-rules.pro
# Reanimated
-keep class com.swmansion.reanimated.** { *; }
-keepclassmembers class com.swmansion.reanimated.** { *; }

# Stripe
-keep class com.stripe.** { *; }
-dontwarn com.stripe.**

# Google Play Billing
-keep class com.android.billingclient.** { *; }
-keep class com.google.android.play.core.** { *; }

Clean Build Debug

cd android
./gradlew clean assembleDebug --no-daemon
cd ..

Library Migrations

Follow the dependency upgrade order from Safe Configuration Path. For each library:

  1. Bump version in package.json (respect peerDependency ranges).
  2. Run npm install / yarn install.
  3. Run cd ios && pod install && cd .. if the library has native iOS code.
  4. Build Debug on both platforms.
  5. Run pilot flow.
  6. Commit with message: upgrade: <library>@<version> – <intent>.

PeerDependency watchouts: react-native-reanimated and react-native-gesture-handler often lock to specific RN minor versions. react-native-stripe-sdk and react-native-google-play-billing enforce minSdk / compileSdk floors. Read their package.json peerDependencies before upgrading.

Verification & Diagnostics

Prove the upgrade works with repeatable checks. Start in Debug; graduate to Release on physical devices.

Debug Build & Pilot Flow

# Android
npx react-native run-android

# iOS Simulator
npx react-native run-ios

Execute pilot flow: Launch → Navigate → Login (API 200, token stored) → Fetch Profile (render, pull-to-refresh).

Logs & Diagnostics

# Android: filter RN/JS logs
adb logcat *:S ReactNative:V ReactNativeJS:V

# iOS: recent logs (replace YourApp)
log show --predicate 'process == "YourApp"' --style syslog --last 2m

# Metro cache reset
npx react-native start --reset-cache

Static Checks

npm test -- --watchAll=false
npx tsc --noEmit
npx eslint .

Release Build Smoke Test (Physical Device Required)

# Android Release (includes JS bundle)
./gradlew assembleRelease bundleReleaseJsAndAssets

# iOS Release (CI-friendly, no signing)
xcodebuild -workspace ios/YourApp.xcworkspace \
  -scheme YourApp \
  -configuration Release \
  -sdk iphoneos \
  -derivedDataPath ios/build \
  CODE_SIGNING_ALLOWED=NO \
  clean build

Install Release builds on physical devices. Test Hermes JIT/AOT, billing flows, Stripe, and background/foreground transitions.

Performance Validation (Constructed Examples)

  • Cold start: Add console.time('bootstrap') at app entry; console.timeEnd('bootstrap') in root component useEffect. Extract from adb logcat / os_log. Compare before/after; drift within 10–15% is acceptable.
  • Frame drops: Enable why-did-you-render in Debug or capture Systrace (npx react-native profile-hermes).
  • Bundle size:
  npx react-native bundle --dev false --platform ios \
    --entry-file index.js \
    --bundle-output /tmp/main.jsbundle \
    --assets-dest /tmp
  # Analyze with hermes-bytecode-analyzer if Hermes enabled

Security & Compliance Checks

  • Verify TLS 1.2+ on all network requests (Charles/ProxyMan).
  • Confirm no __DEV__ flags, console.log of secrets, or debuggable true in Release AndroidManifest.xml / Entitlements.plist.
  • Validate Stripe / Google Play Billing SDK versions meet current store policy minimums.

Verification Table (Adapt to Your Project)

CheckHow to RunPass CriteriaNotes
Build Android Debug./gradlew assembleDebugBuild succeeds, app launchesConstructed example
Build iOS Debugxcodebuild clean build (simulator)Simulator launches appConstructed example
Pilot flow worksManual navigationNo redbox; UI renders; API 200Constructed example
Logs cleanadb logcat, log showNo crash/severe warningsConstructed example
Static checks passnpm test, tsc, eslintZero failuresConstructed example
Build Android Release./gradlew assembleRelease bundleReleaseJsAndAssetsAPK/AAB produced; installs on devicePhysical device
Build iOS Releasexcodebuild -configuration Release.app produced; installs on devicePhysical device
Release pilot flowPhysical device testNo crashes; billing/Stripe workHermes AOT, real network
Performance baselineconsole.time cold startWithin 10–15% of pre-upgradeConstructed example
Security scanProxy TLS, grep __DEV__TLS 1.2+; no debug leaksConstructed example

Failure Modes & Recovery

Targeted diagnostics for common upgrade symptoms.

SymptomLikely CauseAction & Commands
CocoaPods spec/CDN errorStale repo or lockfilecd ios && rm -rf Pods Podfile.lock && pod repo update && pod install && cd ..
Xcode duplicate symbols / Flipper crashFlipper version mismatchDisable Flipper in Podfile: :flipper_configuration => FlipperConfiguration.disabled; rebuild; re-enable after aligning.
Android duplicate classTransitive dependency conflictcd android && ./gradlew :app:dependencies --configuration debugCompileClasspath > debugDeps.txt && cd ..; align versions.
Hermes bytecode mismatch / launch crashInconsistent Hermes flag or stale cachesEnsure enableHermes / :hermes_enabled identical; cd ios && rm -rf build Pods Podfile.lock && pod install && cd ..; cd android && ./gradlew clean && cd ..; npx react-native start --reset-cache.
Metro "Unable to resolve module"Stale cache / watchmanrm -rf node_modules && npm ci && npx react-native start --reset-cache; watchman watch-del-all.
Xcode "Command PhaseScriptExecution failed" (Hermes dSYM)dSYM generation raceClean derived data; ensure DEBUG_INFORMATION_FORMAT = dwarf-with-dsym only for Release; disable Hermes dSYM in Debug if persistent.
Android "Manifest merger failed"minSdk / targetSdk conflictRaise minSdkVersion in android/app/build.gradle to satisfy all libraries; ensure tools:node="replace" for conflicting permissions.
ProGuard/R8 crashes (Release only)Missing keep rulesAdd keep rules for reanimated, Stripe, Play Billing (see Android Alignment); test Release build after each addition.
Release-only network/TLS failureCleartext traffic or cert pinningCheck android:usesCleartextTraffic; verify network_security_config.xml; test with ProxyMan/Charles.

Rollback Procedures

Local Rollback (Fast)

# Reset to baseline tag
git reset --hard pre-rn-X.Y.Z

# Restore JS deps
npm ci --prefer-offline # or yarn install --frozen-lockfile

# iOS clean rebuild
cd ios
rm -rf Pods Podfile.lock build
pod install
cd ..

# Android clean rebuild
cd android
./gradlew clean
cd ..

CI Rollback

  • Revert the upgrade PR/merge commit.
  • Trigger pipeline on main; it will use frozen lockfiles and cached toolchains to rebuild the prior version.
  • Verify artifact publication (TestFlight, Play Console internal track).

Production Rollback Strategies

  • Staged rollout: Pause or decrease rollout percentage in Play Console / App Store Connect when crash rate spikes.
  • Feature flags: Disable risky library features remotely (LaunchDarkly, Firebase Remote Config, custom) while keeping app version live.
  • OTA JS update (CodePush, Expo Updates, custom): Revert JS bundle only if native ABI is unchanged. If native modules were added/removed or RN version changed native interfaces, ship a hotfix binary.

Post-Rollback Validation

  • Prior version builds and launches on both platforms.
  • Pilot flow passes; logs clean.
  • Crash rates and store reviews return to baseline.

Operations Checklist

Preparation

  • [ ] Create branch upgrade/rn-X.Y.Z; tag baseline pre-rn-X.Y.Z.
  • [ ] Complete inventory: Node, pkg mgr, RN, iOS/Android toolchains, Hermes, critical libs, CI parity.
  • [ ] Define narrow pilot flow with pass/fail checks.

Upgrade Steps

  • [ ] Bump react-native; npm install / yarn add.
  • [ ] Apply template diffs: npx react-native upgrade X.Y.Z.
  • [ ] iOS: pod install; confirm Hermes; build Debug.
  • [ ] Android: update Gradle wrapper & AGP; confirm SDK levels; build Debug.
  • [ ] Upgrade critical libraries one by one (order per Safe Configuration Path); commit after each.

Verification

  • [ ] Launch both platforms; run pilot flow end-to-end.
  • [ ] Check logs: no crashes/severe warnings.
  • [ ] Run tests: unit, TypeScript, lint.
  • [ ] Sanity-check performance (cold start, navigation).
  • [ ] Validate REST API, Stripe, Google Play Billing on physical devices.

Stabilization

  • [ ] Build Release variants locally once Debug is clean.
  • [ ] Re-run pilot and extended smoke tests on Release builds.
  • [ ] Document changes and any manual steps.

Recovery Readiness

  • [ ] Baseline tag reachable; lockfiles committed.
  • [ ] Clean-rebuild commands documented and tested.
  • [ ] Feature flags configured for risky areas.

Handoff

  • [ ] Share inventory, diffs, verification results.
  • [ ] Plan rollout with monitoring and rollback trigger.

CI/CD Integration

Run inventory, verification, and build steps in pipeline. Fail fast on regressions. Publish artifacts for QA.

GitHub Actions Snippet (Constructed)

# .github/workflows/rn-upgrade-validation.yml
name: RN Upgrade Validation
on:
  pull_request:
    branches: [main]
    paths:
      - 'package.json'
      - 'yarn.lock'
      - 'ios/**'
      - 'android/**'

jobs:
  validate:
    runs-on: macos-latest # or self-hosted with Xcode/Android SDK
    timeout-minutes: 60
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0 # needed for git diff / tags

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20.12'
          cache: 'yarn'

      - name: Setup Java
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '17'

      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.2'
          bundler-cache: true

      - name: Cache CocoaPods
        uses: actions/cache@v4
        with:
          path: ios/Pods
          key: pods-${{ hashFiles('ios/Podfile.lock') }}

      - name: Cache Gradle
        uses: actions/cache@v4
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: gradle-${{ hashFiles('android/gradle/wrapper/gradle-wrapper.properties', 'android/build.gradle') }}

      - name: Install JS deps
        run: yarn install --frozen-lockfile

      - name: Inventory (baseline capture)
        run: |
          node -v
          yarn -v
          node -p "require('./package.json').dependencies['react-native']"
          xcodebuild -version
          pod --version
          cat android/gradle/wrapper/gradle-wrapper.properties
          grep "com.android.tools.build:gradle" android/build.gradle

      - name: TypeScript & Lint
        run: |
          npx tsc --noEmit
          npx eslint .

      - name: Unit Tests
        run: yarn test --watchAll=false --ci

      - name: iOS Debug Build
        run: |
          cd ios
          pod install --repo-update
          cd ..
          xcodebuild -workspace ios/YourApp.xcworkspace \
            -scheme YourApp \
            -configuration Debug \
            -sdk iphonesimulator \
            -derivedDataPath ios/build \
            clean build

      - name: Android Debug Build
        run: |
          cd android
          ./gradlew assembleDebug --no-daemon
          cd ..

      - name: Upload Android Debug APK
        uses: actions/upload-artifact@v4
        with:
          name: app-debug
          path: android/app/build/outputs/apk/debug/app-debug.apk

      # Optional: Release builds on tagged commits or manual dispatch
      # - name: Android Release Build
      #   if: github.event_name == 'workflow_dispatch'
      #   run: cd android && ./gradlew assembleRelease bundleReleaseJsAndAssets --no-daemon

Bitrise / CircleCI equivalents: Mirror the same steps—cache CocoaPods/Gradle, freeze lockfiles, run typecheck/lint/test, build Debug on both platforms, publish artifacts. Gate Release builds on manual approval or tag push.

Security & Compliance Notes

  • Transport security: Verify all network requests use TLS 1.2+; no cleartext traffic in Release. Inspect Network Security Config (Android) and NSAppTransportSecurity (iOS).
  • Debug flags: Ensure android:debuggable="false" in Release AndroidManifest.xml; DEBUG=0 / __DEV__=false in JS bundle; no console.log of PII/tokens.
  • Billing compliance: Stripe SDK and Google Play Billing Library must meet current store minimums (e.g., Play Billing Library 6+ for Android 14 target). Test purchase flows on physical devices with test accounts.
  • Entitlements & permissions: Audit Entitlements.plist and AndroidManifest.xml for unused permissions (location, camera, microphone) after native module upgrades.
  • Supply chain: npm audit / yarn audit post-upgrade; pin transitive dependencies if critical CVEs appear.

Conclusion

You now have a practical, low-risk path to upgrade React Native: make the current state explicit with a precise inventory; change one bounded slice at a time, starting with a narrow pilot flow; align iOS and Android templates and toolchains, then upgrade libraries in dependency order; verify outcomes with Debug builds, static checks, Release builds on physical devices, performance baselines, and security scans; recover quickly using committed baseline tag, frozen lockfiles, clean-native rebuilds, and feature flags. Repeat this process for each version step, and you will reduce surprises, shorten failure recovery, and keep shipping while modernizing your app.

Related Research

Article Quality Score

Reader usefulness 97%
  • check_circle Reader-ready guide
  • check_circle Practical examples included
  • check_circle Clean SEO article URL