React Native CI with GitHub Actions & FlyCI

You can streamline your React Native application development with a robust CI/CD pipeline using GitHub Actions and FlyCI's specialized macOS runners. This guide provides a step-by-step walkthrough to automate your build and test processes.

All the code used in the guide is publicly available in the flyci-reactnative-guide repository 🔗.

Prerequisites

  • A React Native project set up and running locally.
  • A GitHub account and basic understanding of GitHub repositories.
  • Yarn or npm for managing JavaScript packages.

Step 1: Initialize Your React Native Project

Ensure your React Native application is functioning correctly on both iOS and Android platforms. Your local development environment should be set up with all the necessary dependencies.

Read more about how to configure your environment and create a ReactNative app. 🔗

Step 2: Create a GitHub Repository

Create a new repository on GitHub to host your project's source code. This repository will be the backbone of your CI/CD pipeline, leveraging GitHub Actions.

Read more about how to create a GitHub repository. 🔗

Step 3: Integrate with FlyCI

To utilize FlyCI's macOS runners, ensure you've configured your GitHub project to use FlyCI. This involves

Read more about setting up FlyCI runners.

Step 4: Set Up GitHub Actions Workflow

Preparation

In you package.json, make sure you have scripts for building iOS and Android as follows:

"scripts": {
    ...
    "build-android": "react-native build-android",
    "build-ios": "react-native build-ios",
    ...
}
"scripts": {
    ...
    "build-android": "react-native build-android",
    "build-ios": "react-native build-ios",
    ...
}

We need them to run the build steps in the GitHub Actions workflow.

Create the workflow file

In your GitHub repository, create a .github/workflows directory. Inside this directory, add a YAML file (e.g., ci.yml) to define your CI pipeline. The initial workflow will include the following steps:

  • Checkout repository
  • Install Node.js dependencies
  • Install CocoaPods dependencies
  • Run tests
  • Build iOS app
  • Build Android app

Below is a basic example of the above mentioned steps:

name: "[CI] React Native"
 
on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
  # `workflow_dispatch` allows to run the workflow manually
  # when selecting it in the `Actions tab`
  workflow_dispatch:
 
jobs:
  ci:
    runs-on: flyci-macos-14-m2
    steps:
      - uses: actions/checkout@v4
 
      - name: Install dependencies
        run: |
          set -euxo pipefail
          yarn install --frozen-lockfile
 
      - name: Install pods
        working-directory: ios
        run: |
          set -euxo pipefail
          bundle exec pod install
 
      - name: Run tests
        run: |
          set -euxo pipefail
          yarn test
 
      - name: Build iOS
        run: |
          set -euxo pipefail
          yarn build-ios
 
      - name: Build Android
        run: |
          set -euxo pipefail
          yarn build-android
name: "[CI] React Native"
 
on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
  # `workflow_dispatch` allows to run the workflow manually
  # when selecting it in the `Actions tab`
  workflow_dispatch:
 
jobs:
  ci:
    runs-on: flyci-macos-14-m2
    steps:
      - uses: actions/checkout@v4
 
      - name: Install dependencies
        run: |
          set -euxo pipefail
          yarn install --frozen-lockfile
 
      - name: Install pods
        working-directory: ios
        run: |
          set -euxo pipefail
          bundle exec pod install
 
      - name: Run tests
        run: |
          set -euxo pipefail
          yarn test
 
      - name: Build iOS
        run: |
          set -euxo pipefail
          yarn build-ios
 
      - name: Build Android
        run: |
          set -euxo pipefail
          yarn build-android

Why using --frozen-lockfile when installing dependencies?

With --frozen-lockfile the step will fail if yarn.lock file needs to be updated. The flag ensures reproducible dependencies in CI builds. If the step fails, you need to update your yarn.lock locally, commit it to source control and run the CI again.

Read more about yarn install 🔗

Configuring FlyCI macOS Runners

In the runs-on field, use the specific FlyCI macOS runner label that suits your project's requirements (e.g., flyci-macos-14-m2). Selecting the right runner involves balancing CPU cores, RAM, and storage capabilities against your project's needs.

Read more about how to choose the right runner for your project.

Step 5: Cache Node.js and CocoaPods dependencies

After you've run your first workflow, you might notice that Node.js dependencies and CocoaPods are installed on every run and it takes minutes. Since dependencies don't change on every commit, you can speed up jobs by caching Node.js and CocoaPods dependencies.

To cache dependencies, follow the steps below:

  • In the root directory of your project, add a .nvmrc file with the version of Node.js you need. It is used by the setup-node action.
  • In the root directory of your project, add a .ruby-version file with the version of ruby you need. It is used by the setup-ruby action.
  • Add the following steps in your workflow, right after uses: actions/checkout@v4:
- uses: actions/setup-node@v4
  with:
    node-version-file: ".nvmrc"
    cache: "yarn"
 
- uses: ruby/setup-ruby@v1
  with:
    bundler-cache: true
 
---
- name: Cache Pods
  uses: actions/cache@v4
  id: pods-cache
  with:
    path: ./ios/Pods
    key: ${{ runner.os }}-pods-${{ hashFiles('ios/Podfile.lock') }}
- uses: actions/setup-node@v4
  with:
    node-version-file: ".nvmrc"
    cache: "yarn"
 
- uses: ruby/setup-ruby@v1
  with:
    bundler-cache: true
 
---
- name: Cache Pods
  uses: actions/cache@v4
  id: pods-cache
  with:
    path: ./ios/Pods
    key: ${{ runner.os }}-pods-${{ hashFiles('ios/Podfile.lock') }}

Having the hash of Podfile.lock added to the cache key, ensures that any change in the Podfile will generate a new cache key and thus install the new dependencies and cache them.

Read more about actions/setup-node. 🔗
Read more about actions/setup-ruby. 🔗

Step 6: Optimize iOS build

By default, React Native apps are shipped with Flipper - a debugging tool to help developers debug and profile their React Native apps. However, Flipper is not required in CI, so we can safely disable it. To do that, update the Install pods by adding an env variable NO_FLIPPER: 1:

- name: Install pods
  working-directory: ios
  env:
    NO_FLIPPER: 1
  run: |
    set -euxo pipefail
    bundle exec pod install
- name: Install pods
  working-directory: ios
  env:
    NO_FLIPPER: 1
  run: |
    set -euxo pipefail
    bundle exec pod install

Read more about disabling Flipper for iOS 🔗

Step 7: Ensure code consistency with ESLint and Prettier

It's a good practice to use tools as ESLint and Prettier to ensure code consistency and enforce coding standards. Having that in place, allows to run lint checks in your GitHub Actions workflows. We won't fall into details how to configure ESLint and Prettier. You can follow this React Native, ESLint and Prettier guide 🔗 to do it.

Once you have enforced your coding standards, you can add a check for them to the workflow as a separate step before running the tests:

- name: Run eslint
  run: |
    set -euxo pipefail
    yarn lint
 
- name: Run tests
  run: ...
- name: Run eslint
  run: |
    set -euxo pipefail
    yarn lint
 
- name: Run tests
  run: ...

Step 8: Monitor and Optimize Your CI Pipeline

After committing your GitHub Actions workflow, monitor the pipeline's execution through GitHub's Actions tab and FlyCI's dashboard. Use these tools to identify bottlenecks, troubleshoot failures, and optimize your pipeline for speed and efficiency.

To optimize further you can:

  • utilize parallel job execution to reduce test execution time. For example, you can split iOS and Android builds into separate workflows and build them independently. This way you won't need to wait for installing CocoaPods when building for Android. Also, having separate workflows allows running them simultaneously and get advantage of multiple runners at once.
  • regularly update your dependencies and tools to leverage performance improvements and new features.

Conclusion

By following these steps, you've established a powerful CI pipeline for your React Native project, leveraging GitHub Actions and FlyCI's macOS runners. This setup not only automates your testing and build processes but also ensures your applications are consistently ready for deployment, matching the efficiency and depth of industry-leading practices.

Additional Resources