skip to content

A two-platform React Native social app's CI build takes 25 minutes; which React Native build settings would you change to cut it, and in what order?

level: seniorimportance: should knowfreq 30%

answer

  1. measure phases before tuning
  2. iOS: keep prebuilt core on
  3. Android: ABI matrix for test builds
  4. configuration cache and a Maven mirror
  5. compiler cache with content hashing

basics

~20 s

Measure which platform and phase dominate first. Then make sure iOS uses the prebuilt core, split Android test builds by ABI with reactNativeArchitectures, enable Gradle's configuration cache, point downloads at a Maven mirror, and add ccache or sccache where native compilation remains.

solid answer

~50 s

I start by timing each phase on both platforms — dependency download, `pod install`, Gradle configuration, native compilation, Swift and Kotlin compilation, bundling — because the fix depends on where the 25 minutes go. On iOS the biggest lever is usually confirming the build uses precompiled React Native core: it is the default since 0.84, but a leftover `RCT_USE_PREBUILT_RNCORE=0` in the CI script silently compiles it from source. On Android, test builds can run as a matrix with `-PreactNativeArchitectures` per ABI, as the React Native docs suggest, while the release bundle keeps every ABI. Then I enable `org.gradle.configuration-cache=true`, route dependency downloads through our mirror with `exclusiveEnterpriseRepository` and `ENTERPRISE_REPOSITORY`, and, for native compilation that remains, add ccache with `compiler_check content` or a shared sccache. After each change I re-measure, so I keep only what paid off.

go deeper

for a junior

Know the main levers: prebuilt iOS core, building fewer Android ABIs, and caching native compilation.

for a middle

Explain what each lever attacks — compilation, configuration, downloads — and the settings that switch each one on.

for a senior

Show a measured, ordered plan for a slow two-platform pipeline, keeping release artefacts complete and removing stale flags.

for a principal

Weigh build-speed investment against reproducibility: shared caches and mirrors, who owns build infrastructure, and how gains are tracked over upgrades.

## Start by measuring A 25-minute pipeline for two platforms is a sum of phases, and each lever attacks a different one. Before changing anything, time: - **dependency resolution** — npm install, `pod install`, Gradle and Maven downloads; - **Gradle configuration** — evaluating every build script; - **native compilation** — C, C++ and Objective-C, per ABI on Android; - **Swift, Kotlin and Java compilation**; - **JavaScript bundling and Hermes bytecode compilation**; - **packaging and signing**. Most CI systems show step durations; Gradle's build scans or `--profile` report and Xcode's build timing summary break the native builds down further. Rank the phases and fix the largest first. ## The levers React Native provides | Lever | Platform | Attacks | Setting | |---|---|---|---| | Precompiled core | iOS | compiling React Native core and its dependencies | default since 0.84; check nothing sets `RCT_USE_PREBUILT_RNCORE=0` | | ABI selection | Android | compiling native code four times | `-PreactNativeArchitectures=...` per job | | Configuration cache | Android | re-evaluating build scripts | `org.gradle.configuration-cache=true` (since 0.79) | | Maven mirror | both | downloading artifacts from the internet | `exclusiveEnterpriseRepository` (Gradle), `ENTERPRISE_REPOSITORY` (pods) | | Compiler cache | both | recompiling unchanged native code | ccache with `compiler_check content`, or sccache | ## A sensible order 1. **iOS: confirm the precompiled core is in use.** It is the largest single saving on a clean iOS build and costs nothing when it is already on. `pod install` logs that it is using the prebuilt versions; a flag left over from an old Hermes or legacy-architecture workaround turns it off. 2. **Android: stop compiling ABIs you do not need.** For test and pull-request builds that only run on one emulator or device type, build that ABI. The React Native docs also suggest a CI matrix, one job per ABI, to parallelise — for test artefacts. The **release** bundle must contain every ABI, so keep one full build for it. 3. **Android: enable the configuration cache** in `android/gradle.properties`. It helps repeat builds within a job and across jobs that preserve Gradle's state. 4. **Both: use a mirror if your organisation has one.** `exclusiveEnterpriseRepository` makes Gradle fetch every dependency from it exclusively; `ENTERPRISE_REPOSITORY` points the iOS prebuilt-artifact downloads at the same mirror. 5. **Both: cache what is still compiled.** ccache needs `compiler_check content` on CI because checkouts reset timestamps, and the docs warn about poisoned caches; for many runners and developers, **sccache** shares one distributed cache. ## A worked example Suppose the measurements for the social app split the 25 minutes like this (illustrative numbers, not a benchmark): - **iOS job, 14 minutes:** 8 of them compiling React Native core, because the pipeline still exports `RCT_USE_PREBUILT_RNCORE=0` from a Hermes workaround two upgrades ago. - **Android job, 11 minutes:** 6 of them compiling native code for four ABIs, 2 configuring Gradle, 1 downloading dependencies. Removing the stale iOS flag attacks the largest block at no cost. Building one ABI for pull-request builds cuts most of the Android native time, and the configuration cache trims the configuration phase on repeat builds. The mirror and a compiler cache come last, because by then the remaining time is small — and each is justified only if a re-measurement shows it pays. ## What each lever does not do - **Prebuilt core** does not speed up your own Swift, or third-party native modules. - **ABI selection** does nothing for Kotlin, Java or Swift. - **The configuration cache** skips configuration, not task execution. - **A compiler cache** cannot help the first build on an empty cache, and gains little where prebuilt binaries already removed the compilation. Saving and restoring CocoaPods, Gradle and ccache folders between CI runs is a separate, CI-level concern, and it compounds with every lever above. ## Keeping the gains - **Record the before-and-after timings** of each change, and drop changes that did not pay. - **Keep flags in one place** — the pipeline definition or `gradle.properties` — so a local workaround never leaks into CI. - **Re-check after upgrades**: each React Native release can change defaults, as 0.84 did for iOS. ## Common mistakes - Turning on everything at once, then being unable to tell which change helped or broke the build. - Building a one-ABI release artefact because the test matrix was copied into the release job. - Adding ccache on iOS without measuring, when prebuilt core had already removed most of the C++. - Treating a mirror as a cache: it speeds downloads, not compilation.

  • After the per-ABI matrix, how do you still produce a React Native Android release that works on every device?
    Keep a separate release job that builds without a `reactNativeArchitectures` override, so the app bundle contains native libraries for every ABI. The matrix speeds up test and pull-request builds that run on one emulator type; it must never feed the store upload.
  • Your iOS CI build got slower after a React Native upgrade even though prebuilt core should be on. What do you check?
    Read the `pod install` output for the line saying it uses prebuilt React Native Core and Dependencies. If it is missing, look for `RCT_USE_PREBUILT_RNCORE=0` or `RCT_USE_RN_DEP=0` in the pipeline's environment or the Podfile — often left from a Hermes or legacy-architecture workaround — and for a mirror that lacks the new version's artifacts.

saying these in an interview costs you the question

  • Turn on every build optimisation at once and compare the total
  • Use the one-ABI matrix artefacts for the store release
  • ccache on CI works out of the box after restoring its folder
  • The configuration cache skips compiling unchanged Kotlin
  • A Maven mirror reduces native compilation time