All posts
React Native
3 min read

Shipping faster with Expo EAS: the setup I reach for

A pragmatic EAS build and update setup — three profiles, OTA updates on the right channels, and the guardrails that keep releases boring.

Every Expo project I start eventually grows the same EAS configuration, so I've stopped reinventing it. Here's the shape that's held up across a few apps.

Three build profiles, no more

eas.json tends to sprawl. I keep it to three profiles that map to three questions: "is this for me", "is this for the team", or "is this for users?"

{
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal"
    },
    "preview": {
      "distribution": "internal",
      "channel": "preview"
    },
    "production": {
      "channel": "production",
      "autoIncrement": true
    }
  }
}
  • development — a dev client I install once and iterate against with Fast Refresh.
  • preview — an internal build the team can install from a link before a release.
  • production — store builds, with autoIncrement so I never hand-bump versions.

OTA updates on matching channels

The point of EAS Update is to fix a typo without a full store round-trip. The trap is pushing an update to the wrong audience. Tie each channel to its build profile and never cross the streams:

# Ship a JS-only fix to the internal testers
eas update --channel preview --message "Fix empty-state copy"

# Promote the same commit to everyone
eas update --channel production --message "Fix empty-state copy"

Rule of thumb: native change → new build; JS/asset change → update. If you touched a native module or config plugin, an OTA update won't carry it.

Guardrails that keep releases boring

A few small habits prevent most incidents:

  1. Gate production updates on the runtime version. An update built against a newer native runtime should not land on an older binary. Set runtimeVersion to { "policy": "appVersion" } (or fingerprint) so mismatched updates are simply not served.
  2. Preview before promote. I never eas update --channel production cold — it goes to preview first, gets a smoke test, then I promote the exact same commit.
  3. Watch the rollout. After a production update, glance at install and error counts before walking away. A regression is cheap to roll back and expensive to ignore.

Wiring it into CI

Once the profiles exist, CI is thin. On a tagged commit, build production and submit; on a push to main, publish a preview update. Keep the credentials in EAS, not in the workflow file, and let eas build/eas submit do the heavy lifting.

None of this is clever — that's the point. The goal is releases that are so routine they're forgettable, which frees up attention for the actual product.