Speed Up React Native in GitHub Actions
Last updated
To speed up React Native in GitHub Actions, run lint, type-check and Jest on Linux, build Android on Linux with Gradle caching, and use macOS only for the iOS build with CocoaPods and DerivedData cached. The expensive part of React Native CI is the native builds, and the single biggest saving is not running them on every push.
This is the React Native version of our general guide to speeding up GitHub Actions.
Split by what needs a Mac
A React Native repo has three kinds of work:
- JavaScript checks. ESLint, TypeScript and Jest. Linux.
- Android build. Gradle and the Android SDK. Linux.
- iOS build. Xcode and CocoaPods. macOS only.
Run (1) and (2) on every PR. Run (3) on main, release branches or when ios/ and native dependencies change. Hosted macOS minutes are priced at a higher rate than Linux minutes, so see macOS runners for iOS CI for the numbers and options.
JavaScript checks
js:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
cache: yarn
- run: yarn install --frozen-lockfile
- run: yarn tsc --noEmit
- run: yarn lint
- run: yarn jest --ci --maxWorkers=50%
actions/setup-node caches the package manager's download cache (npm, yarn or pnpm) as its README says. Jest with --ci avoids interactive behavior and writing snapshots. For slow Jest runs, see Jest slow, including --shard.
Android: Gradle and the Metro bundle
The Android build runs Gradle and bundles JavaScript with Metro. Use gradle/actions/setup-gradle for the Gradle cache (the Gradle guide covers its behavior and the properties file), and set up Java for the version your Android Gradle Plugin needs:
android:
runs-on: ubuntu-latest
timeout-minutes: 40
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
cache: yarn
- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: 17
- uses: gradle/actions/setup-gradle@v6
- run: yarn install --frozen-lockfile
- run: ./gradlew assembleRelease
working-directory: android
Settings that help in android/gradle.properties:
org.gradle.caching=trueandorg.gradle.parallel=true.org.gradle.jvmargs=-Xmx4gfor the heap; React Native builds are memory hungry.reactNativeArchitectures=arm64-v8afor CI test builds. The default builds four ABIs (armeabi-v7a,arm64-v8a,x86,x86_64), and native code compiles once per ABI, so limiting it can cut the C++ compile time by a large share. Build all ABIs only for release artifacts.- Hermes is the default engine in current React Native, and its bytecode compile step is quick; there is little to tune.
Metro caches transformed modules in a temp folder. On a fresh VM it starts empty. You can set a stable cache location and save it with actions/cache, though the gain is modest compared with the Gradle and native build caches. Measure before you add the step.
iOS: CocoaPods and derived data
pod install downloads and unpacks every native dependency. Cache the Pods folder keyed on Podfile.lock. Add ios/build or a DerivedData path if you build from scratch often; it is a larger cache and can hit GitHub's size limits, so see cache limits.
ios:
runs-on: macos-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
cache: yarn
- run: yarn install --frozen-lockfile
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- uses: actions/cache@v6
with:
path: ios/Pods
key: pods-${{ runner.os }}-${{ hashFiles('ios/Podfile.lock') }}
- run: bundle exec pod install
working-directory: ios
- run: |
xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp \
-configuration Release -sdk iphonesimulator \
-destination 'generic/platform=iOS Simulator' \
CODE_SIGNING_ALLOWED=NO build
Use ruby/setup-ruby with bundler-cache: true if you manage CocoaPods through a Gemfile; the action caches the gems for you. Build for the simulator and with signing disabled for PR checks, since that is faster and needs no certificates. Do device builds and signing only in release jobs. For deeper Xcode tips, see speed up Xcode builds in CI.
Expo and EAS
If you use Expo, you can offload native builds to EAS Build and keep GitHub Actions for JS checks only. That moves the cost somewhere else; compare the price of the service against runner minutes before deciding. Either way, the principle holds: native builds are rare and expensive, so trigger them deliberately.
Detect native changes
A change to JavaScript files cannot break the Gradle or Xcode build in most cases. Use path filters or a changed-files check so the native jobs run only when android/, ios/, package.json or the lockfile changes. Pair that with concurrency to cancel superseded runs.
What more CPU cores buy you
Gradle and the C++ compile for native modules use many cores, so Android builds gain from a bigger Linux machine. Xcode builds depend on the Mac. We have no React Native row in our benchmarks, so we do not quote a speedup. compiler.dev has Linux machines at 8 vCPU / 16 GB by default ($0.012 per minute) and macOS in early access: Apple M4 Pro 7-core VM at $0.028 per minute and 14-core at $0.055 per minute, or a dedicated host from $3,595 a month. For macOS-only work, Namespace and others also sell macOS runners; compare current prices on their pages before choosing. See pricing and runner pricing.
Switching a Linux job is one line, runs-on: [self-hosted, Linux, compiler-ci]. Related: the React Native template, the Flutter guide and GitHub Actions caching.
FAQ
Can I build React Native for iOS on Linux?
No. iOS builds need Xcode and macOS. Run JS checks and the Android build on Linux.
How do I cache CocoaPods in GitHub Actions?
Cache ios/Pods with actions/cache, keyed on ios/Podfile.lock, and run pod install after it.
Why is my React Native Android build so slow?
Usually it compiles native code for four ABIs and has no Gradle cache. Limit reactNativeArchitectures for CI and use setup-gradle.
Should I run iOS builds on every pull request?
Only if your team ships native changes often. Many teams run iOS on main and when native files change, which saves the most expensive minutes.
Related
- Add a CI Speed Badge to Your READMEShow how fast your CI is with a README badge: the median GitHub Actions run time for your repository, updated automatically. Free for public repos.
- Speed Up .NET in GitHub ActionsSpeed up .NET builds in GitHub Actions: NuGet caching with setup-dotnet, --no-restore, --no-build, parallel dotnet test, and SQL Server services.
- Speed Up Bazel in GitHub ActionsSpeed up Bazel in GitHub Actions with setup-bazel, a disk cache, repository cache, a remote cache, and the right --jobs setting. Working YAML.
- Speed Up Django in GitHub ActionsSpeed up Django tests in GitHub Actions with pip or uv caching, pytest-xdist, --reuse-db, a Postgres service, and test splitting. Working YAML.