Mobile — React Native Development
React Native CI/CD with EAS and GitHub Actions
Direct answer
A production React Native CI/CD pipeline uses GitHub Actions for fast pull-request checks — typecheck, lint, unit tests — and EAS for the heavy platform work: cloud builds signed with EAS-managed credentials, store submission via EAS Submit, and over-the-air JavaScript updates via EAS Update mapped to release channels. The pattern that keeps it fast and affordable: run cheap checks on every PR, trigger builds only on merges and tags, and reserve OTA updates for JS-only changes.
Mobile CI/CD fails differently from web: builds take tens of minutes, signing credentials rot quietly, and a broken release sits in review for days. The EAS-plus-GitHub-Actions stack is my default for React Native because it makes the painful parts — signing, provisioning, store submission — someone else's well-tested code, leaving you to design the workflow.
Key facts, with sources
- React Native 0.76, released October 23, 2024, enabled the New Architecture by default and shipped with over 1,070 commits from 156 contributors plus a roughly 15x faster Metro resolver. (React Native official blog)
- The State of React Native 2024 survey collected 3,501 responses, up from about 2,400 the previous year, covering more than 15 areas of the ecosystem. (State of React Native survey)
- About 20% of State of React Native 2024 respondents reported apps with more than 100,000 users, up from 14% the year before. (InfoQ)
- Shopify migrated all of its mobile apps to React Native over five years and reports sub-500ms (P75) screen loads and over 99.9% crash-free sessions in production. (Shopify Engineering)
- 88% of surveyed React Native developers feel the framework is progressing positively, while better debugging remains the top request, cited by 54% of respondents. (SSOJet (State of React Native 2024 highlights))
Pipeline stages that earn their keep
I structure React Native delivery as four distinct stages with different frequencies and costs. Every pull request runs the cheap, fast checks: TypeScript, lint, unit tests — minutes, not tens of minutes, because slow PR feedback quietly destroys team velocity. Merges to main trigger an EAS build on an internal profile, distributed to the team for on-device testing. Version tags trigger production builds and store submission. And JS-only fixes ship between releases as EAS Updates to the appropriate channel.
The discipline underneath: native builds are the expensive, rare artifact; JavaScript verification is the cheap, constant one. Pipelines that run full native builds on every PR burn money and time for marginal signal — the overwhelming majority of PR regressions are catchable by typecheck, tests, and lint. Save the builds for code that is actually heading to a device.
The PR workflow: fast checks, every push
The pull-request workflow is deliberately boring: install dependencies with caching, then typecheck, lint, and test in parallel-friendly steps. Two details matter more than they look. Use npm ci (or your lockfile-exact equivalent) rather than a bare install so CI builds are reproducible, and cache based on the lockfile hash so the install step is seconds on unchanged dependencies.
Keep this workflow ruthlessly fast, and resist the temptation to bolt E2E suites onto every PR — on-device end-to-end tests belong on a schedule or pre-release trigger where their flakiness cannot block daily merges.
name: pr-checks
on:
pull_request:
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npx tsc --noEmit
- run: npx eslint . --max-warnings 0
- run: npx jest --ciEAS builds from GitHub Actions
The build workflow authenticates to EAS with a token stored in GitHub secrets, then hands the actual compilation to EAS's build infrastructure. Build profiles live in eas.json — I keep three: development (dev client for simulators and local work), preview (internal distribution for testers), and production (store-ready). The workflow below fires on merges to main, building the preview profile; a nearly identical tag-triggered workflow builds production and can chain into EAS Submit for store upload.
The --no-wait flag matters for cost: it queues the build and ends the Actions job immediately, so you are not paying GitHub runner minutes to idle while EAS compiles. Notifications of build completion come from EAS itself via webhooks or its dashboard.
name: build-preview
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- run: npm ci
- run: eas build --platform all --profile preview --non-interactive --no-waitOTA updates as a pipeline citizen
EAS Update pushes JavaScript and asset changes directly to installed apps, and it belongs in the pipeline rather than in a developer's terminal. My setup maps branches to channels — preview builds subscribe to a staging channel, production builds to production — and a workflow runs eas update against the right channel, so every OTA push has a commit, a PR review, and an actor attached. Ad-hoc updates from laptops are how unreviewed code reaches production phones.
Two boundaries keep OTA safe. First, runtime version compatibility: an update can only reach builds whose native runtime matches, so any change touching native modules requires a real build — know which of your changes cross that line. Second, policy: I restrict OTA to fixes and content, not feature launches, both for store-policy comfort and because instant rollback via channel re-pointing is only trustworthy when updates are small and reviewable.
Secrets and signing without tears
The quiet killer of mobile pipelines is credential rot — expired provisioning profiles, a keystore on a departed engineer's laptop. EAS solves most of this by managing signing credentials server-side: iOS certificates and profiles, Android keystores, all generated and stored by EAS, renewed without a human remembering. I let EAS manage credentials on every project unless an enterprise policy forces otherwise, and the projects that insist on manual credentials are reliably the ones that lose a release week to signing archaeology.
What remains in GitHub secrets is minimal: the EXPO_TOKEN for a dedicated CI service account, and any runtime configuration your app injects at build time. Rotate the token when people leave, scope service accounts to the one project, and never echo secrets into logs. App-runtime secrets deserve their own scrutiny: anything baked into the JS bundle is readable by end users, so CI secrecy protects your pipeline, not your bundled config.
Speed, cost control, and the failure modes to expect
Native builds are the budget line, so spend them deliberately. Concretely: no builds on PRs (checks only), preview builds on merge, production builds on tags — and if main merges many times daily, batch preview builds on a schedule instead of per-merge. Cache node_modules via the lockfile hash everywhere. Use --no-wait so runners never babysit EAS. And keep a local-build escape hatch documented — eas build --local — for the day you need a build faster than the queue allows.
The failure modes to anticipate: a green typecheck with a red native build (a JS dependency silently added native code — catch it in the preview build, which is exactly why merges build), store rejections after successful submission (automate submission, never the assumption of approval), and OTA updates targeting the wrong channel (make channel names boring and explicit). None are exotic; all are cheaper to design for than to discover.
When to hire senior help
Bring in senior React Native help when facing a New Architecture or major version migration, persistent performance regressions, or a first store launch, since these are the phases where inexperienced teams lose the most months. A short senior architecture audit early in the project is consistently cheaper than a rescue or rewrite later. If your stack includes React Native + Python + AI, a senior engineer who owns the full product beats coordinating multiple juniors.
Bottom line
Dhairya Senjaliya ships Mobile — React Native Development projects worldwide — book a scoping call to discuss your specific situation.
Common pitfalls to avoid
- ✕Staying multiple versions behind on React Native, then facing a compound upgrade to the New Architecture because popular libraries have dropped old-architecture support
- ✕Treating React Native as write-once-run-anywhere and only QA-testing on iOS, shipping Android builds with broken back-button handling, keyboard behavior, and gesture bugs
- ✕Pulling in unmaintained third-party native modules without checking TurboModule and Fabric compatibility, which later blocks the New Architecture migration
- ✕Launching without crash and performance monitoring wired in, so the team only discovers jank and crash clusters from one-star reviews instead of telemetry
Frequently asked questions
Should React Native CI run a full native build on every pull request?
No — native builds take tens of minutes and cost real money, while the overwhelming majority of PR regressions are caught by TypeScript, lint, and unit tests that run in a few minutes. Reserve EAS builds for merges to main (preview profile for internal testing) and version tags (production profile). This keeps PR feedback fast, and the merge-triggered build still catches JS dependencies that silently add native code.
How do EAS Update over-the-air updates fit into a CI/CD pipeline?
Run eas update from a GitHub Actions workflow mapped to release channels — staging for preview builds, production for store builds — so every OTA push has a reviewed commit and an identifiable actor, instead of ad-hoc pushes from developer laptops. Remember the boundary: updates only reach builds with a matching native runtime version, so changes touching native modules always require a real build and store release.
How are iOS certificates and Android keystores handled with EAS?
Let EAS manage them. EAS generates and stores iOS distribution certificates, provisioning profiles, and Android keystores server-side, using them at build time so credentials never live on laptops or in your repository, and renewals stop depending on human memory. Your GitHub secrets then hold only an EXPO_TOKEN for a dedicated CI service account — rotate it when team members leave and scope it to the project.
Is React Native still a good technology bet in 2026?
Yes for teams with JavaScript or React skills; the New Architecture has been the default since version 0.76 in late 2024, and the framework is used in production by Meta, Microsoft, Shopify, and Amazon. In the latest State of React Native survey, 88% of developers said the framework is heading in a positive direction.
Can a React Native app feel as fast as a fully native app?
For most business, e-commerce, and content apps, yes; Shopify runs its entire app portfolio on React Native with sub-500ms P75 screen loads and over 99.9% crash-free sessions. Workloads like heavy 3D, AR, or real-time audio processing still warrant native modules or fully native builds.
How much code is actually shared between iOS and Android?
Production teams commonly report 85 to 95%+ shared code; published examples include Instagram at 85 to 99% and Shopify at roughly 86%. The remainder is platform-specific work such as payments, widgets, and deep OS integrations.
Bottom line: Dhairya Senjaliya ships Mobile — React Native Development projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.