diff --git a/docs/platforms/react-native/manual-setup/expo.mdx b/docs/platforms/react-native/manual-setup/expo.mdx index 3afafffef16bb0..d7159b016a6b81 100644 --- a/docs/platforms/react-native/manual-setup/expo.mdx +++ b/docs/platforms/react-native/manual-setup/expo.mdx @@ -148,9 +148,20 @@ To verify that everything is working as expected, build the `Release` version of ## Next Steps -- [Learn how to upload source maps for native builds and Expo Updates](/platforms/react-native/sourcemaps/uploading/expo/) -- [Add automatic tracing with Expo Router](/platforms/react-native/tracing/instrumentation/expo-router/) -- [Configure the Sentry Android Gradle Plugin](/platforms/react-native/manual-setup/expo/gradle/) +### Expo-Specific Features + +- [Expo Updates](/platforms/react-native/manual-setup/expo/expo-updates/) — Automatic OTA update context, searchable tags, and emergency launch alerts +- [EAS Build Hooks](/platforms/react-native/manual-setup/expo/eas-build-hooks/) — Capture build failures and lifecycle events from EAS Build +- [Sentry Android Gradle Plugin](/platforms/react-native/manual-setup/expo/gradle/) — Advanced Android build configuration + +### Source Maps and Releases + +- [Upload source maps for native builds and Expo Updates](/platforms/react-native/sourcemaps/uploading/expo/) + +### Performance + +- [Expo Router tracing](/platforms/react-native/tracing/instrumentation/expo-router/) — Navigation transitions, performance spans, and prefetch instrumentation +- [Expo Image and Asset tracing](/platforms/react-native/tracing/instrumentation/expo-resources/) — Automatic spans for `expo-image` and `expo-asset` ## Notes diff --git a/docs/platforms/react-native/manual-setup/expo/eas-build-hooks.mdx b/docs/platforms/react-native/manual-setup/expo/eas-build-hooks.mdx new file mode 100644 index 00000000000000..06cc812ad7ebea --- /dev/null +++ b/docs/platforms/react-native/manual-setup/expo/eas-build-hooks.mdx @@ -0,0 +1,79 @@ +--- +title: EAS Build Hooks +description: "Capture EAS build failures and lifecycle events in Sentry." +sidebar_order: 10 +--- + +EAS Build runs your app builds on Expo's infrastructure. When a build fails, the error can be hard to diagnose — it happens outside your local environment and outside your app. EAS Build Hooks let you send build lifecycle events directly to Sentry so you can track failures, correlate them with releases, and get notified when production builds break. + +## Setup + +Add the Sentry EAS build hook scripts to your `package.json`: + +```json {filename:package.json} +{ + "scripts": { + "eas-build-on-error": "sentry-eas-build-on-error", + "eas-build-on-success": "sentry-eas-build-on-success", + "eas-build-on-complete": "sentry-eas-build-on-complete" + } +} +``` + +EAS Build automatically runs `package.json` scripts with these names at the corresponding lifecycle events. See the [Expo npm hooks documentation](https://docs.expo.dev/build-reference/npm-hooks/) for more details. + +Set your DSN as an EAS secret or environment variable so it's available during builds: + +```bash +eas secret:create --scope project --name SENTRY_DSN --value +``` + +## What Gets Captured + +**`eas-build-on-error`** — Sends an `EASBuildError` event to Sentry when a build fails. The event includes: + +- Build platform (`ios` / `android`) +- Build profile (for example, `production`, `preview`) +- Build ID, project ID, git commit hash +- Whether the build ran on CI + +**`eas-build-on-success`** — Optionally sends an info event when a build succeeds. Disabled by default; enable it with `SENTRY_EAS_BUILD_CAPTURE_SUCCESS=true`. + +**`eas-build-on-complete`** — A single hook that captures either a failure or success event depending on the build outcome. Use this instead of `on-error` + `on-success` if you prefer a single hook entry point. + +All events are tagged with `eas.*` tags so you can filter and group them in Sentry. + +## Environment Variables + +| Variable | Required | Description | +|---|---|---| +| `SENTRY_DSN` | Yes | Your project's DSN | +| `SENTRY_EAS_BUILD_CAPTURE_SUCCESS` | No | Set to `true` to capture successful builds | +| `SENTRY_EAS_BUILD_TAGS` | No | JSON object of additional tags, for example `{"team":"mobile"}` | +| `SENTRY_EAS_BUILD_ERROR_MESSAGE` | No | Custom error message for failed builds | +| `SENTRY_EAS_BUILD_SUCCESS_MESSAGE` | No | Custom message for successful builds | +| `SENTRY_RELEASE` | No | Override the release name (defaults to `{appVersion}+{buildNumber}`) | + +## Environment Files + +The hook automatically loads environment variables from the following sources (without overwriting values already set by EAS): + +1. `@expo/env` (if available) +2. `.env` file in the project root (via `dotenv`, if available) +3. `.env.sentry-build-plugin` file in the project root + +This means you can store the DSN in `.env.sentry-build-plugin` locally, and use an EAS secret in CI — the hook will use whichever value is already set. + +## Using `on-complete` Instead of Separate Hooks + +If you only need to track failures but want the option to add success tracking later, `eas-build-on-complete` is the most flexible option. It reads `EAS_BUILD_STATUS` (set by EAS) to determine the outcome and captures the appropriate event: + +```json {filename:package.json} +{ + "scripts": { + "eas-build-on-complete": "sentry-eas-build-on-complete" + } +} +``` + +To also capture successful builds, set `SENTRY_EAS_BUILD_CAPTURE_SUCCESS=true` in your build environment. diff --git a/docs/platforms/react-native/manual-setup/expo/expo-updates.mdx b/docs/platforms/react-native/manual-setup/expo/expo-updates.mdx new file mode 100644 index 00000000000000..0035f49ded7383 --- /dev/null +++ b/docs/platforms/react-native/manual-setup/expo/expo-updates.mdx @@ -0,0 +1,74 @@ +--- +title: Expo Updates +description: "Automatic context and alerts for Expo OTA updates." +sidebar_order: 20 +--- + +When you ship OTA updates with Expo Updates, events captured by Sentry are automatically enriched with information about which update is running. This lets you filter issues by update channel, runtime version, or update ID — and get alerted when an emergency launch occurs. + +These integrations are enabled by default in Expo projects. No additional setup is required. + +## Expo Updates Context + +Every Sentry event includes an `ota_updates` context with the current update state: + +| Field | Description | +|---|---| +| `update_id` | UUID of the currently running OTA update | +| `channel` | Expo Updates channel (for example, `production`, `staging`) | +| `runtime_version` | Runtime version of the update | +| `is_enabled` | Whether Expo Updates is enabled | +| `is_embedded_launch` | Whether the app launched from the embedded bundle | +| `is_using_embedded_assets` | Whether the app is using embedded assets | +| `is_emergency_launch` | Whether this was an emergency launch (fallback to embedded) | +| `emergency_launch_reason` | Reason for the emergency launch (if applicable) | +| `check_automatically` | Automatic update check policy | +| `launch_duration` | How long the update launch took (ms) | +| `created_at` | When the update was created | + +## Searchable Tags + +The SDK also sets the following tags on every event so you can filter and group issues in Sentry: + +| Tag | Value | +|---|---| +| `expo.updates.update_id` | UUID of the running update | +| `expo.updates.channel` | Update channel name | +| `expo.updates.runtime_version` | Runtime version string | + +To find all issues from a specific update channel, use `expo.updates.channel:production` in the Sentry issue search. + +## Emergency Launch Warnings + +Expo Updates performs an _emergency launch_ when it fails to load the latest OTA update and falls back to the embedded bundle. This can silently degrade user experience. + +When the SDK detects an emergency launch at startup, it automatically sends a `warning`-level event to Sentry with: + +- Message: `Expo Updates emergency launch: ` (or `Expo Updates emergency launch` if no reason is available) +- Tag: `expo.updates.emergency_launch: true` + +You can set up a Sentry alert on this tag to get notified whenever an emergency launch happens in production. + + + +Emergency launch detection only runs in native builds, not in Expo Go. + + + +## Expo Constants Context + +The SDK automatically captures Expo Constants as an `expo_constants` context on every event. This provides metadata about the execution environment and app configuration: + +| Field | Description | +|---|---| +| `execution_environment` | Where the app is running (`storeClient`, `standalone`, `bare`) | +| `app_ownership` | Whether the app runs in Expo Go or standalone | +| `debug_mode` | Whether debug mode is enabled | +| `expo_version` | Expo client version | +| `expo_runtime_version` | Runtime version from app config | +| `session_id` | Current session ID | +| `app_name` | App name from `app.json` | +| `app_slug` | App slug from `app.json` | +| `app_version` | App version from `app.json` | +| `expo_sdk_version` | Expo SDK version | +| `eas_project_id` | EAS project ID | diff --git a/docs/platforms/react-native/tracing/instrumentation/expo-resources.mdx b/docs/platforms/react-native/tracing/instrumentation/expo-resources.mdx new file mode 100644 index 00000000000000..8f4a1fd993cbf7 --- /dev/null +++ b/docs/platforms/react-native/tracing/instrumentation/expo-resources.mdx @@ -0,0 +1,44 @@ +--- +title: Expo Image and Asset Instrumentation +description: "Automatic performance spans for expo-image and expo-asset." +sidebar_order: 70 +--- + +Image and asset loading times directly affect how quickly your app feels responsive. The SDK can automatically create performance spans for `expo-image` and `expo-asset` operations so you can see exactly how long prefetching and loading takes within your existing traces. + +## Expo Image + +Wrap the `Image` class from `expo-image` once at app startup to instrument `Image.prefetch()` and `Image.loadAsync()`: + +```javascript {filename:App.js} +import { Image } from 'expo-image'; +import * as Sentry from '@sentry/react-native'; + +Sentry.wrapExpoImage(Image); +``` + +After wrapping, every call to `Image.prefetch()` creates a `resource.image.prefetch` span, and every call to `Image.loadAsync()` creates a `resource.image.load` span. The spans are automatically linked to the active trace. + +Span attributes include: + +- `image.url` — the image URL (for single-image operations) +- `image.url_count` — number of URLs (for batch prefetch) + +## Expo Asset + +Wrap the `Asset` class from `expo-asset` once at app startup to instrument `Asset.loadAsync()`: + +```javascript {filename:App.js} +import { Asset } from 'expo-asset'; +import * as Sentry from '@sentry/react-native'; + +Sentry.wrapExpoAsset(Asset); +``` + +After wrapping, every call to `Asset.loadAsync()` creates a `resource.asset` span with an `asset.count` attribute. + +## Notes + +- Both `wrapExpoImage` and `wrapExpoAsset` are safe to call multiple times — the SDK guards against double-wrapping. +- Spans are only created when a trace is active. If you're not using [tracing](/platforms/react-native/tracing/), no spans are created and there's no overhead. +- `expo-image` and `expo-asset` are peer dependencies — the SDK does not require them to be installed. diff --git a/docs/platforms/react-native/tracing/instrumentation/expo-router.mdx b/docs/platforms/react-native/tracing/instrumentation/expo-router.mdx index 06bb3ed948a508..aee83eb1a7f5ca 100644 --- a/docs/platforms/react-native/tracing/instrumentation/expo-router.mdx +++ b/docs/platforms/react-native/tracing/instrumentation/expo-router.mdx @@ -69,6 +69,34 @@ This option will enable automatic measuring of the time to initial display for e This ensures that transactions that are from routes that've been seen and don't have any spans, are not being sampled. This removes a lot of clutter, making it so that most back navigation transactions are now ignored. The default value is `true`. +## Prefetch Instrumentation + + + +`router.prefetch()` requires **Expo Router v5 (Expo SDK 53) or later**. On older versions the method does not exist; calling it will throw a runtime error. + + + +Expo Router's `router.prefetch()` preloads a route before the user navigates to it. By default, these prefetch calls are invisible in your traces. Wrapping the router with `Sentry.wrapExpoRouter()` adds an automatic `navigation.prefetch` span for each call so you can see prefetch timing alongside your other navigation spans. + +```javascript {filename:app/(tabs)/index.tsx} +import { useRouter } from 'expo-router'; +import * as Sentry from '@sentry/react-native'; + +function HomeScreen() { + const router = Sentry.wrapExpoRouter(useRouter()); + + return ( +