Mobile — Expo Development

Debugging Production Expo Crashes with Sentry

Direct answer

Use @sentry/react-native with its Expo config plugin: it captures both JavaScript errors and native crashes, and the plugin wires source map upload into your builds so you get readable stack traces instead of minified Hermes frames. The two steps teams skip are putting the Sentry auth token in EAS secrets so release builds upload source maps automatically, and tagging events with the EAS Update ID so every crash maps to the exact JS bundle that caused it.

A production crash you can't read is a crash you can't fix, and default Expo production crashes are unreadable — minified frames with no symbols. Wiring Sentry into an Expo app properly takes about an hour, and the difference in incident response is night and day. Here's my production setup.

Key facts, with sources

  • 71% of State of React Native 2024 respondents reported using Expo's EAS Build, ahead of manual builds with Xcode (59.7%) and Android Studio (54.5%). (InfoQ)
  • Expo SDK 52, released November 12, 2024, enabled the New Architecture by default for all new projects, and Expo Go for SDK 52 and higher supports only the New Architecture. (Expo Changelog)
  • The official React Native documentation now recommends starting new apps with a framework, naming Expo, rather than initializing a bare project. (React Native documentation)
  • EAS build caching can speed up subsequent Android and iOS builds by up to 30% and is available to all users at no additional cost as of the SDK 57 cycle. (Expo Changelog)
  • Doctolib runs Expo tooling without EAS to scale developer experience on a healthcare app serving 90 million users, showing Expo's open-source tools work independently of its paid cloud services. (Doctolib Engineering (Medium))

Why production Expo crashes are opaque by default

Production JS bundles are minified, and under Hermes your stack traces reference compiled bytecode positions rather than your source. Without source maps uploaded and matched to the exact bundle, a fatal error renders as a single mangled frame — useless for diagnosis. Native crashes are worse: they never reach the JS layer at all, so a JS-only error handler misses them entirely, and to you they look like users silently churning.

This is why I treat crash reporting as launch-blocking infrastructure, not post-launch polish. The first week after launch is exactly when you have the most unknown crash surface and the least tolerance for flying blind. Setting up symbolication after a bad release means the crashes that hurt you most are the ones you'll never decode.

Install and initialize

The current path is the official @sentry/react-native SDK, which supports Expo directly — the older sentry-expo wrapper is deprecated. Install it, add the config plugin to app config, and initialize as early as possible in the app lifecycle: for expo-router apps that means the root layout, wrapped with Sentry's helper so render errors during startup are captured too.

Initialize outside any component and before navigation mounts. A surprising number of missed crashes trace back to Sentry initializing after the code that crashed.

Sentry init with EAS Update correlation
import * as Sentry from '@sentry/react-native';
import * as Updates from 'expo-updates';

Sentry.init({
  dsn: process.env.EXPO_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 0.1,
  environment: __DEV__ ? 'development' : 'production',
});

Sentry.setTag('updateId', Updates.updateId ?? 'embedded');
Sentry.setTag('updateChannel', Updates.channel ?? 'none');

// Root component (e.g. expo-router root layout):
export default Sentry.wrap(App);

Source maps: the step that makes traces readable

The config plugin integrates source map upload into the build, and it needs credentials: your Sentry org, project, and an auth token. The token must not live in source control — put it in EAS secrets so cloud builds can upload symbols, and in local env for local release builds. Once wired, every EAS build uploads the artifacts Sentry needs to symbolicate that binary's crashes automatically.

The recurring failure mode is partial setup: builds work, crashes arrive, but traces stay minified because the token was missing in CI and the upload silently didn't happen. After your first release build, force a test crash and confirm you see your actual file names and line numbers before trusting the pipeline.

app.json — Sentry config plugin
{
  "expo": {
    "plugins": [
      [
        "@sentry/react-native/expo",
        {
          "organization": "your-org",
          "project": "your-mobile-app"
        }
      ]
    ]
  }
}

Correlating crashes with OTA updates

EAS Update changes the debugging question from 'which app version crashed' to 'which JS bundle crashed' — one binary version can be running several different bundles across your fleet. That's what the update tags in the init snippet solve: with the update ID and channel attached to every event, you can filter Sentry by a specific OTA release and see immediately whether a crash spike started with an update you shipped an hour ago.

This turns OTA incident response into a tight loop: spike appears filtered to the new update ID, you run a channel rollback, and you watch the spike die out as clients revert. Without the tags you're correlating timestamps and guessing. For any app using OTA updates seriously, I consider this correlation mandatory, not optional telemetry.

Triage habits that make the data useful

Collection is half the job; the other half is a feed someone actually reads. Breadcrumbs — navigation events, network requests, taps — come largely free with the SDK and routinely matter more than the stack trace, because they tell you what the user did to get there. I add manual breadcrumbs around payment, sync, and other flows where post-incident forensics are likely. Set explicit user context (an ID, not PII) so you can answer 'is this one broken device or everyone' at a glance.

Know the gaps, too: crashes before Sentry initializes, OS-level kills like out-of-memory terminations, and some startup native failures may not appear, so App Store Connect and Play Console crash data remain worth a periodic cross-check. My working ritual is simple — after every release, binary or OTA, watch fresh issues for a day, and treat any new crash signature above a trickle as a rollback trigger rather than a ticket for next sprint.

When to hire senior help

Senior help pays off when deciding between managed and prebuild workflows, setting up EAS-based CI/CD and OTA update channels, or untangling a project that was ejected prematurely. An experienced Expo engineer can usually configure build, submit, and update pipelines in days, a task that costs first-time teams weeks of trial and error. 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 — Expo Development projects worldwide — book a scoping call to discuss your specific situation.

Common pitfalls to avoid

  • Ejecting to the bare workflow at the first native requirement instead of using config plugins and development builds, permanently giving up managed upgrades
  • Testing only in Expo Go and misconfiguring runtime versions for expo-updates, so an OTA update ships JavaScript that crashes against mismatched native binaries
  • Burning EAS build minutes on every commit in CI without caching or local builds, turning a convenience service into a surprise line item
  • Skipping several SDK upgrades in a row, then being forced into a large breaking migration when app store target API level deadlines arrive

Frequently asked questions

Should I use sentry-expo or @sentry/react-native in an Expo app?

Use @sentry/react-native — it supports Expo directly via its config plugin and is the actively maintained path; the separate sentry-expo package is deprecated. Add @sentry/react-native/expo to your plugins in app config, initialize the SDK at the app entry point, and provide a Sentry auth token to your builds so source maps upload automatically.

Why are my Sentry stack traces from Expo still minified?

Source maps for that exact bundle weren't uploaded or didn't match. The usual causes: the Sentry auth token missing from EAS secrets so cloud builds skipped the upload, the config plugin added after the build was made, or an OTA update published without its source maps. Trigger a deliberate test crash after each pipeline change and confirm readable frames before trusting it.

Does Sentry catch native crashes in Expo apps or only JavaScript errors?

Both. @sentry/react-native includes native crash reporting for iOS and Android alongside the JavaScript layer, which matters because native crashes never reach a JS error handler. Coverage isn't absolute — crashes before SDK initialization and OS-level terminations like out-of-memory kills can be missed — so keep an occasional eye on App Store Connect and Play Console crash statistics as a cross-check.

Should we use Expo or plain React Native for a new app?

The React Native docs themselves now recommend starting with a framework, and Expo is the primary one; 71% of surveyed developers already build with EAS. Expo today supports custom native code through development builds and config plugins, so the old limitations that forced teams to avoid it mostly no longer apply.

Does Expo lock us into their platform?

No; expo prebuild can generate standard native iOS and Android projects at any time, and the open-source tooling works without Expo's paid EAS services, as demonstrated by Doctolib running Expo without EAS on an app with 90 million users. EAS Build, Submit, and Updates are optional conveniences, not requirements.

Is Expo production-ready for a serious commercial app?

Yes; Expo Go is only a development sandbox, while production apps ship as normal store binaries built with EAS or locally. Plan for the SDK release cadence of roughly three versions per year, since staying current is required to keep up with store policy and React Native releases.

Bottom line: Dhairya Senjaliya ships Mobile — Expo Development projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.

Sources

Related guides

Keep up with new guides

New deep-dive guides on React Native, Python, and AI ship regularly. Subscribe via RSS or follow on LinkedIn.

Want help implementing this?

30-minute scoping call · Clear milestones · Senior engineer ownership