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
- installing FlyCI GitHub App 🔗 and
- giving it access 🔗 to the corresponding repository in your account or organization.
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
🔗
Good practice
Use set -euxo pipefail
when running shell scripts. Here is why:
-e
ensures the script fails immediately if a command fails-u
ensures the script fails if we try to use an undefined variable-x
adds a trace for each executed command-o pipefail
ensures that if any of the shell commands executed in a pipe fail, the whole script will fail
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.
Good practice
Keep required versions on Node.js and Ruby in the corresponding version files committed to
the source control. For Node.js this is .nvmrc
or .node-version
. For Ruby it is .ruby-version
. This way you
ensure the same versions are used by the team and the CI.
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 thesetup-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 thesetup-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.