Create preview branches for React Native apps with Expo + EAS from your CI with GitHub Actions

Working on a React Native application using EAS + Expo Go.

The Context

The Problem

I have used preview deployments with NextJS and Vercel before. They allow any non-technical stakeholder to validate a feature before it gets into the main branch (defects are found earlier and the main branch remains releasable at any time).
There’s nothing like this for React Native!
EAS with Expo Go provides a platform which makes it easy to put a WIP app in stakeholders hands which updates with continuous deployments.
Goal: why not recreate the DevX of Vercel for React Native with EAS + Expo?!

The Solution

  1. Create a GitHub actions to create a deployment for every pull request created.
      • Run eas update against ${{ github.head_ref }} which is the name of the branch (unique per feature and provided by GitHub Actions to the context of the workflow).
  1. Create a second action to cleanup the EAS channel and branch on merge of the PR, run:
      • eas channel:delete (credit to Mo for finding the secret eas channel:delete command in the EAS CLI to clean up the branches on merge)
      • eas branch:delete
  1. We also did some clever stuff to print out to the pull request the QR code to scan to get the deployment directly in the Expo Go app via the QR code scanner - you’ll need to read the full article get all the details!

The Code

# .github/workflows/expo-preview-deploy.yml name: Deploy EAS Preview Branch for Feature on: push: branches-ignore: - main jobs: update: name: EAS Update runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 # Expo installation steps - name: Publish update run: eas update --branch ${{ github.head_ref }} --auto
# .github/workflows/expo-preview-cleanup.yml on: pull_request: types: - closed jobs: update: name: Cleanup EAS Preview Channel and Branch for Feature runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 # Expo installation steps - name: Delete Channel run: eas channel:delete ${{ github.head_ref }} --non-interactive - name: Delete Branch # Temporary workaround: `eas channel:delete` always throws an error if: success() || failure() run: eas branch:delete ${{ github.head_ref }} --non-interactive