React Native Build
React Native allows you to build cross-platform iOS and Android apps using JavaScript and React. This recipe shows how to install dependencies, manage native code, compile your app for both platforms, and sign for distribution.
Basic Dual-Target Build
Section titled “Basic Dual-Target Build”This is the simplest multi-job pipeline that builds both iOS and Android:
name: React Native Buildplatform: react-native
environment: node: lts xcode: "16.4" android_sdk: 34
triggers: - push - pull_request
jobs: build-ios: name: Build iOS steps: - name: Install node modules run: npm install
- name: Install pods run: | cd ios pod install
- name: Build run: | cd ios xcodebuild -workspace App.xcworkspace -scheme App build
build-android: name: Build Android steps: - name: Install node modules run: npm install
- name: Build run: | cd android ./gradlew assembleReleaseWith Testing and Code Signing
Section titled “With Testing and Code Signing”Here’s a more complete example with shared tests and signed release builds:
name: React Native CI/CDplatform: react-native
environment: node: "20.11" # Explicit version for consistency xcode: "16.4" android_sdk: 34 ruby: "3.2" # Optional: for Bundler/CocoaPods
triggers: - push - pull_request
jobs: test: name: Tests & Lint steps: - name: Install node modules run: npm install
- name: Lint run: npm run lint
- name: Type check run: npm run type-check
- name: Run tests run: npm test
build-ios: name: Build iOS (Signed) needs: [test] steps: - name: Install node modules run: npm install
- name: Install pods run: | cd ios pod install
- name: Build for release run: | cd ios xcodebuild \ -workspace App.xcworkspace \ -scheme App \ -configuration Release \ -derivedDataPath build \ build
artifacts: - ios/build/**/*.app - ios/build/**/*.dSYM
build-android: name: Build Android (Signed) needs: [test] steps: - name: Install node modules run: npm install
- name: Build release bundle run: | cd android ./gradlew bundleRelease
artifacts: - android/app/build/outputs/bundle/release/**/*.aabKey points:
environment.node: "20.11"— Explicit Node version; uselts,latest, or specific semverenvironment.ruby: "3.2"— Optional; useful if using Ruby-based tools (Bundler, CocoaPods with complex dependencies)needs: [test]— Both iOS and Android builds wait for tests to pass- Branch gating — To run signed builds only on main, set
triggers: [{ event: push, branches: [main] }]or use step-levelif: success()conditions - Dependency caching — npm cache is shared across all jobs; subsequent
npm installcalls are nearly instant - Metro cache — Metro bundler cache at
node_modules/.cache/metrois automatically cached per platform and branch
Monorepo with Yarn Workspaces
Section titled “Monorepo with Yarn Workspaces”For monorepos where the app is in a subdirectory:
name: React Native Monorepoplatform: react-native
environment: node: lts xcode: "16.4" android_sdk: 34
triggers: - push
jobs: build-ios: name: Build iOS steps: - name: Install workspace dependencies run: yarn install working_directory: .
- name: Install pods run: pod install working_directory: apps/mobile/ios
- name: Build run: xcodebuild -workspace App.xcworkspace -scheme App build working_directory: apps/mobile/ios
build-android: name: Build Android steps: - name: Install workspace dependencies run: yarn install working_directory: .
- name: Build run: ./gradlew assembleRelease working_directory: apps/mobile/androidParallel Native Module Builds
Section titled “Parallel Native Module Builds”If your React Native app depends on native modules requiring compilation (e.g., react-native-reanimated, react-native-skia):
name: React Native with Native Modulesplatform: react-native
environment: node: lts xcode: "16.4" android_sdk: 34
triggers: - push
jobs: build-ios: name: Build iOS steps: - name: Install node modules run: npm install
- name: Install pods (with native modules) run: | cd ios pod install
- name: Build run: | cd ios xcodebuild \ -workspace App.xcworkspace \ -scheme App \ -configuration Release \ build
artifacts: - ios/build/**/*.app
build-android: name: Build Android steps: - name: Install node modules run: npm install
- name: Build run: | cd android ./gradlew bundleRelease
artifacts: - android/app/build/outputs/bundle/release/**/*.aabCocoaPods and Gradle both support transparent native module compilation. Both dependency caches (pods and gradle) are managed automatically.
Expo (Bare Workflow) Build
Section titled “Expo (Bare Workflow) Build”For Expo with the bare React Native workflow:
name: Expo Bare Buildplatform: react-native
environment: node: lts xcode: "16.4" android_sdk: 34
triggers: - push
jobs: build-ios: name: Build iOS via EAS steps: - name: Install dependencies run: npm install
- name: Build with EAS run: npx eas build --platform ios --non-interactive
build-android: name: Build Android via EAS steps: - name: Install dependencies run: npm install
- name: Build with EAS run: npx eas build --platform android --non-interactiveFor Expo projects, use eas build commands directly (no eas-cli setup needed—it’s installed via npm).
Metro Bundler Caching
Section titled “Metro Bundler Caching”Metro (React Native’s bundler) cache is automatically cached between builds at node_modules/.cache/metro. The cache key includes the platform and branch, so:
mainbranch iOS builds share one Metro cachefeature/xbranch iOS builds share a different cache- Android caches separately from iOS
No additional configuration needed—just use npm install as normal, and Metro will use cached artifacts on subsequent builds.
To force a clean Metro build:
- name: Clean Metro cache run: rm -rf node_modules/.cache/metro
- name: Build run: npm install && cd ios && xcodebuild -workspace App.xcworkspace -scheme App buildKey Points
Section titled “Key Points”- npm install: Installs all Node.js dependencies listed in
package.json, including React Native itself - pod install: Resolves native iOS dependencies in
ios/Podfile(required even for React Native) - ./gradlew: Builds Android via Gradle; RunnerHub caches gradle wrappers and build outputs
working_directory: Use for monorepos to run commands in subdirectories- Multi-job pattern: Separate iOS and Android builds allow parallel execution and clear logs
- Dependency caching: npm cache is shared across all jobs in the pipeline
- Conditional builds: Use
triggers: [{ event: push, branches: [main] }]to gate signed releases to main, or use step-levelif:conditions
Troubleshooting
Section titled “Troubleshooting”Pod conflicts: If you encounter pod version mismatches, try:
- name: Update pod repo run: | cd ios pod repo update pod install --repo-updateGradle build failures: Clear gradle cache:
- name: Clean Gradle run: | cd android ./gradlew clean ./gradlew assembleReleaseNode version issues: If a native module requires a specific Node version, pin it explicitly:
environment: node: "18.20.3" # Exact versionMetro issues: If Metro bundler seems stale, clear the cache manually:
- name: Clean Metro run: rm -rf node_modules/.cache/metro && npm installNext Steps
Section titled “Next Steps”- Learn about Code Signing to prepare for App Store and Google Play distribution
- Set up Deploy Configurations to automatically upload to TestFlight, App Store, Google Play, or Firebase
- Explore Multi-Job Pipelines for complex workflows
- Review the YAML Reference for all available options