skip to content

Store Release

Shipping a React Native app means signed native builds: Android keystores and bundles, iOS certificates and archives, store review and staged rollout. Interviewers probe what breaks in release.

part ofReact Nativeoverview, primer and where to startread it →
on this pageshow

explore

questions

19

In a React Native Android release, what is the difference between an AAB and an APK, and which one does Google Play expect?

level: juniorimportance: must knowfreq 65%

answer

  1. what you upload vs what installs
  2. bundleRelease vs assembleRelease
  3. Play generates per-device split APKs
  4. AAB requires Play App Signing
  5. APK still useful off-store

basics

~20 s

An APK is the package a device installs; an AAB (Android App Bundle) is a publishing format Google Play turns into per-device APKs. React Native's release command runs bundleRelease to build the AAB Play requires for new apps.

solid answer

~40 s

An APK is the file a device actually installs. An **AAB** (Android App Bundle) is a publishing artifact: you upload it and Google Play generates optimized split APKs per device, carrying only the CPU architecture, screen density and language resources that device needs. In a React Native project, `npx react-native build-android --mode=release` runs Gradle's `bundleRelease` and writes `android/app/build/outputs/bundle/release/app-release.aab`, while `./gradlew assembleRelease` produces an APK. Both release variants embed the Metro JS bundle, so neither needs Metro at runtime. Google Play requires AABs for new apps and accepts them only with Play App Signing configured, because Play re-signs the APKs it generates. APKs remain the tool for sideloading, QA devices and stores that do not take bundles.

code

bash · 9 lines
bash
# Play upload artifact (AAB) via Gradle bundleRelease
npx react-native build-android --mode=release
# -> android/app/build/outputs/bundle/release/app-release.aab

# Installable APK for QA devices or other stores
cd android && ./gradlew assembleRelease

# Build, install and launch the release variant on a connected device
npm run android -- --mode="release"

go deeper

for a junior

Know which file you upload (the .aab from bundleRelease) and which file a phone installs (an .apk). Remember the release command and where the output lands.

for a middle

Explain that Play builds per-device split APKs from the bundle, which is why it must hold the app signing key, and that release variants embed the JS bundle instead of using Metro.

for a senior

Reason about what an AAB does and does not shrink in a React Native app: native ABIs and density resources yes, the single JS bundle no. Diagnose a release that ships without its bundle.

for a principal

Weigh distribution channels together: an AAB for Play, APKs for off-store QA or other stores, and the signing and versioning consequences each channel adds to the release process.

## Two formats, two jobs Android has two packaging formats a React Native developer meets at release time, and they answer different questions: **what does a device install?** and **what does a store receive?** | | APK | AAB (Android App Bundle) | |---|---|---| | What it is | An installable package | A publishing artifact, not installable as-is | | Gradle task | `assembleRelease` | `bundleRelease` | | Output path | `android/app/build/outputs/apk/release/` | `android/app/build/outputs/bundle/release/app-release.aab` | | Who installs it | A device, directly | Google Play turns it into split APKs per device | | Native code for all ABIs | In one universal file unless you split | Play delivers only the device's ABI | | Signing | Signed with your key, installed as signed | Signed with your upload key, re-signed by Play | The key idea: an **APK** is the unit of installation, while an **AAB** is the unit of publication. Google Play requires the AAB format for new apps, so for a Play release the AAB is the artifact you build. ## How a React Native project builds each The React Native docs for 0.87 describe the Play path as one command: 1. Configure release signing with an upload keystore (a separate question on this leaf). 2. Run `npx react-native build-android --mode=release`. It calls Gradle's `bundleRelease` task under the hood. 3. Pick up `android/app/build/outputs/bundle/release/app-release.aab` and upload it to the Play Console. For an APK you run `./gradlew assembleRelease` from the `android/` folder instead. To try the release variant on a phone, `npm run android -- --mode="release"` builds, installs and launches it; the docs note that `--mode release` only works once signing is set up. ## What ends up inside a release artifact A release variant differs from the debug build you run every day: - **The JavaScript bundle is embedded.** The React Native Gradle plugin bundles JS for every variant *not* listed in `react { debuggableVariants }`, whose default is `debug` and `debugOptimized`. A release artifact therefore carries `index.android.bundle` in its assets and never talks to Metro. - **Images you `require()` become Android resources.** The bundling task writes them into a generated `res` folder, so they are packaged like any drawable, per screen density. - **Native libraries per ABI.** React Native ships compiled `.so` libraries; the docs note a default APK carries code for `x86`, `x86_64`, `armeabi-v7a` and `arm64-v8a`. - **The signing certificate** of whoever signed the artifact. ## Why an AAB needs Play App Signing With an APK, the signature you apply is the signature the user's device checks. With an AAB, Google Play builds the final APKs itself, so it must sign them itself. That is **Play App Signing**: Google holds the *app signing key*, you sign your uploads with an *upload key*, and Play verifies the upload before re-signing the generated APKs. The React Native docs state it plainly: Play accepts the AAB format only when App Signing by Google Play is configured for the app. ## The scenario: a language-learning app's first Play release Imagine the first release of a language-learning app with lessons in twelve languages and audio for each. - Uploading an AAB means a phone with an `arm64-v8a` CPU and a high-density screen downloads only that ABI's native code and that density's drawables, which keeps the download small. - **Language splits apply to Android string resources** (`res/values-*`). The lesson text your JS imports is inside `index.android.bundle`, so every device gets all of it; to shrink that you would have to load translations at runtime instead. - The QA team, testing on devices outside Play, gets an APK from `assembleRelease`, because an AAB cannot be installed by tapping it. ## Common traps - **Installing the AAB directly.** It is not an installable package; build an APK or use the release run command for local testing. - **Uploading a debug build.** Debug variants load JS from Metro and are signed with the debug keystore; Play refuses them. - **`org.gradle.configureondemand=true` in `gradle.properties`.** The React Native docs warn it makes the release build skip bundling JS and assets, so the app starts with no bundle. - **Assuming the AAB changes the JS side.** Bundle format affects native code, resources and signing; the JS bundle ships whole either way.

  • A React Native release AAB installs from Play but shows 'Unable to load script'; what Gradle setting is a known cause?
    `org.gradle.configureondemand=true` in `gradle.properties`. The React Native docs warn that it makes the release build skip bundling JS and assets, so the binary has no `index.android.bundle` and looks for Metro like a debug build. Remove it and rebuild. Also check that the variant is not listed in `react { debuggableVariants }` (default `debug` and `debugOptimized`), because listed variants are never bundled.
  • When would a React Native team still build ABI-split or universal APKs?
    For stores or channels that take APKs rather than bundles. The docs show an `android { splits { abi { ... } } }` block: `universalApk false` produces one APK per CPU architecture for stores with device targeting, `true` adds one universal APK. Each split APK needs its own distinct `versionCode`, so the version scheme must encode the ABI.
  • Why do release images from require() get a per-density benefit from an AAB, while JS strings do not?
    The React Native Gradle plugin writes required images into a generated `res` folder, so they are ordinary Android drawables that Play can split by screen density. Strings imported by JavaScript are compiled into `index.android.bundle`, a single asset file, so Play cannot split them by language and every device downloads them all.

An AAB is a shop's master catalogue sent to a print house; the print house (Google Play) prints each customer a booklet with only their size and language. An APK is the printed booklet itself, which you can hand to someone directly.

saying these in an interview costs you the question

  • An AAB can be installed on a phone by tapping it, like an APK.
  • The debug build from npm run android is what you upload to Play.
  • A release AAB downloads its JavaScript from Metro on first launch.
  • Play serves every device the same universal APK built from the AAB.
  • You can upload an AAB to Play without Play App Signing.
  • Choosing AAB lets Play split the app's JS translations per language.
open as a page

What changes when a React Native iOS app is built with Xcode's Release configuration instead of Debug?

level: juniorimportance: must knowfreq 60%

basics

~20 s

A Release build embeds the JavaScript as main.jsbundle inside the app, loads it from there instead of from Metro, compiles the JS in production mode and removes the Dev Menu. Archives for the App Store always use Release.

open as a page

How do you sign a React Native Android release with your own upload keystore instead of the template's debug keystore?

level: middleimportance: must knowfreq 55%

basics

~20 s

Generate an upload keystore with keytool, put its path, alias and passwords in MYAPP_UPLOAD_* Gradle properties, add a signingConfigs.release block that reads them, and point buildTypes.release at it; the fresh template signs release with the debug keystore.

open as a page

In a React Native CI pipeline, how do you get the Android upload keystore and iOS signing assets into the build without committing them?

level: middleimportance: must knowfreq 55%

basics

~10 s

Store the keystore, certificate and profile as base64 CI secrets, decode them into temporary files at build time, pass the passwords as Gradle properties or environment variables, and delete everything when the job ends.

open as a page

For a React Native iOS release, what do the distribution certificate and the App Store provisioning profile each do when you archive and upload?

level: middleimportance: must knowfreq 55%

basics

~20 s

The distribution certificate, with its private key in your keychain, proves the build comes from your team. The App Store provisioning profile ties that certificate to one bundle identifier and its entitlements. Xcode signs the archive with both before upload.

open as a page

Do over-the-air JavaScript updates to a React Native app break App Store or Google Play rules?

level: middleimportance: must knowfreq 60%

basics

~20 s

Not by themselves. Both stores allow downloaded interpreted code such as a JS bundle, provided it keeps the reviewed app's purpose, adds no storefront for other code and bypasses no OS security. Native code changes still need a store release.

open as a page

In a React Native app's android/app/build.gradle, how do versionCode and versionName differ, and which must change on every Play upload?

level: juniorimportance: should knowfreq 50%

basics

~20 s

versionCode is an integer Android and Google Play compare to order builds; it must be new and higher on every upload. versionName is a free-form string shown to users and never compared. Both live in defaultConfig in android/app/build.gradle.

open as a page

Why does a React Native app's iOS release build need a macOS CI runner, and what must that runner have installed?

level: juniorimportance: should knowfreq 42%

basics

~20 s

The iOS app is compiled, linked, signed and archived by Xcode's tools, which run only on macOS. The runner needs Xcode (16.1 or newer for React Native 0.87), Ruby with CocoaPods, and Node for the JavaScript bundle step.

open as a page

In a React Native iOS app, how do CFBundleVersion and CFBundleShortVersionString differ, and which must change on every App Store Connect upload?

level: juniorimportance: should knowfreq 45%

basics

~10 s

CFBundleShortVersionString is the user-facing release version, such as 1.4.0; CFBundleVersion is the build number. Each App Store Connect upload needs a build number not used before for that version, so it changes every upload.

open as a page

What common store rejection causes should you check before submitting a React Native app to the App Store and Google Play?

level: juniorimportance: should knowfreq 45%

basics

~20 s

Check that the Release build works without Metro, give reviewers demo credentials, fill or remove purpose strings such as the template's empty location one, keep the privacy manifest complete, and avoid thin web wrappers and other-platform mentions.

open as a page

In a React Native Android project, what does enableProguardInReleaseBuilds change, and why can a release crash after you enable it?

level: middleimportance: should knowfreq 35%

basics

~20 s

enableProguardInReleaseBuilds turns on minifyEnabled for the release build, so R8 shrinks and renames the app's Java and Kotlin bytecode. Code reached only by reflection or JNI can be removed or renamed, crashing the release until keep rules are added to proguard-rules.pro.

open as a page

In CI for a React Native app, what should you cache for node_modules, CocoaPods and Gradle, and what should each cache key be?

level: middleimportance: should knowfreq 40%

basics

~20 s

Cache dependency downloads keyed on their lockfiles: the JavaScript packages on the package lockfile, ios/Pods on Podfile.lock, Ruby gems on Gemfile.lock, and Gradle's caches and wrapper on the Gradle build files. Still run pod install, and never cache signing material.

open as a page

How does App Store phased release differ from a Google Play staged rollout when shipping a React Native app update?

level: middleimportance: should knowfreq 40%

basics

~20 s

App Store phased release spreads an update to auto-updating users over a fixed seven-day schedule you can pause; a Google Play staged rollout reaches a percentage you choose and can halt. Neither downgrades users who already updated.

open as a page

Your React Native team must ship an urgent Android hotfix, but the upload keystore is lost; what happens and how do you recover?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Under Play App Signing only the upload key is lost: installed apps are unaffected, but Play rejects uploads until the account owner resets the upload key, which delays the hotfix. Losing a self-held app signing key ends updates entirely.

open as a page

Two branches of a React Native app both uploaded build 57 and the store rejected one; how should CI assign Android versionCode and the iOS build number instead?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Let one release pipeline own a single increasing counter and inject it at build time: a Gradle property for versionCode and the CURRENT_PROJECT_VERSION build setting for the iOS build number, instead of numbers edited by hand in the repository.

open as a page

For a React Native app, when should CI hand the release build to Fastlane lanes or to EAS Build, and what stays in the CI job?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Hand off to Fastlane when you own the native projects and runners and want scripted signing and upload on them; hand off to EAS Build when Expo should run the builds and hold credentials. CI still tests, gates and triggers.

open as a page

A React Native parking app needs an urgent iOS hotfix, but archiving fails because the App Store provisioning profile expired; what do you check and fix?

level: seniorimportance: should knowfreq 35%

basics

~20 s

An expired profile blocks new builds, not the app already on the App Store. Check whether only the profile expired or the distribution certificate too; regenerate the profile (or a new certificate first), install it, archive and upload.

open as a page

A React Native bank app's redesigned home screen is at 10% on Google Play and mid phased release on iOS when crashes spike; what do you do?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Stop exposure first: halt the Play rollout and pause the iOS phased release. Then switch the redesign off via a remote flag if one exists, diagnose by version, and fix forward with a higher build, since neither store rolls back.

open as a page

What are dSYM files in a React Native iOS archive, and why can native crash reports stay unsymbolicated without them?

level: middleimportance: nice to knowfreq 25%

basics

~20 s

A dSYM holds the debug symbols stripped from the shipped binary, matched to it by UUID. Crash reports map native addresses back to function names only with the matching dSYM from the archive; JavaScript frames need source maps instead.

open as a page