Skip to main content
Back to module
Mobile App Production · Release

Builds, updates, and crash reporting

Reading42mLesson 2 of 3 in this module

Set up build profiles, crash reporting with source maps and safe over-the-air updates.

Why it matters

Once real people use your app, two questions matter every day: "Is it crashing for anyone?" and "How fast can we ship a fix?" Crash reporting answers the first. A repeatable build pipeline and over-the-air (OTA) updates answer the second. Together they turn releases from stressful events into routine work.

Build profiles

With EAS, eas.json defines build profiles, usually three:

{
  "build": {
    "development": { "developmentClient": true, "distribution": "internal" },
    "preview": { "distribution": "internal", "channel": "preview" },
    "production": { "autoIncrement": true, "channel": "production" }
  }
}
  • development: your own dev client for daily work.
  • preview: an internal build that testers install, pointed at a staging API.
  • production: the store build.

Use environment variables (EAS environment variables or app.config.js) for per-profile settings such as the API URL. Never put secrets in them if they end up in the JavaScript bundle; anything the app ships is readable.

You can trigger builds from CI (GitHub Actions or EAS Workflows) on every merge to the main branch, so every build is reproducible and nobody ships from a laptop with uncommitted changes. The free EAS plan has a limited number of builds per month; check expo.dev/pricing (checked on 2026-10-23).

Over-the-air updates

A React Native app has two parts: native code (compiled into the binary) and the JavaScript bundle plus assets. EAS Update lets you publish a new JavaScript bundle that installed apps download on their next launch, without a store review:

npx expo install expo-updates
eas update:configure
eas update --channel production --message "Fix crash on empty cart"

Rules that keep OTA safe:

  • JavaScript and assets only. Changing native code (adding a native library, changing permissions or the SDK version) needs a new store build.
  • Runtime version. Each build has a runtimeVersion; an update is only delivered to builds with a matching one, so a JavaScript bundle never lands on a binary that lacks the native code it needs. The "policy": "fingerprint" setting derives it from your native code automatically.
  • Store policies. Both stores allow interpreted code updates for bug fixes and small improvements, but not changes to the app's core purpose that bypass review. Use OTA for fixes, not to ship a different app.
  • Roll out gradually (EAS Update supports percentage rollouts) and keep the ability to roll back to the previous update.

Crash reporting

Users rarely report crashes; they just leave. A crash reporter catches errors on the device and sends them to a dashboard with the stack trace, device model, OS version, app version and the steps (breadcrumbs) leading up to the crash. Popular choices are Sentry (@sentry/react-native, with an Expo config plugin) and Firebase Crashlytics. Both have free tiers suitable for learning (checked on 2026-10-23).

import * as Sentry from "@sentry/react-native";

Sentry.init({
  dsn: process.env.EXPO_PUBLIC_SENTRY_DSN,   // a DSN is not a secret, but keep it configurable
  environment: __DEV__ ? "development" : "production",
  sendDefaultPii: false,                      // do not send personal data by default
});

export default Sentry.wrap(App);

Two production details matter:

  • Source maps. Production JavaScript is minified, so stack traces look like a.b is not a function at 1:20934. Uploading source maps for each build and update turns them back into your file names and line numbers. The Sentry Expo integration can do this during EAS builds and updates.
  • Privacy. Do not attach tokens, emails or form contents to error reports. Declare crash data in the store privacy forms.

Worked example: a fix in under an hour

  1. 10:02: crash alerts show TypeError: Cannot read properties of undefined (reading 'total') in CartSummary, 3% of sessions, version 1.4.0, only for users with empty carts.
  2. 10:10: the source-mapped stack trace points to cart.items[0].total. You add a guard and a unit test for the empty cart.
  3. 10:25: CI passes. You publish eas update --channel production to 10% of users.
  4. 10:45: the crash rate for the new update is zero; you roll out to 100%.
  5. Later: the native build is unchanged, so no store review was needed.

Common mistakes

  • OTA updates that require native code the installed build does not have (use runtime versions).
  • No source maps, so crash reports are unreadable.
  • Sending personal data in crash reports or logs.
  • Builds made by hand on one laptop, so nobody can reproduce them.
  • No staged rollout, so a bad update reaches everyone.

Quick recap

  • Define development, preview and production build profiles; build from CI.
  • EAS Update ships JavaScript and asset fixes over the air, matched by runtime version, rolled out gradually.
  • Crash reporting with uploaded source maps tells you what broke, where and for whom, without collecting personal data.

Try it

The Do tab has you add crash reporting to your app on a free tier, trigger a test error and, if you want, publish an OTA update.

Also in this lesson: