From 0131518ee17d8e11d8e929dac8ac6aaa77815e18 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Thu, 25 Jun 2026 12:19:39 +0200 Subject: [PATCH] Expo Router docs --- .../integrations/error-boundary.mdx | 6 + .../react-native/manual-setup/expo.mdx | 2 +- .../tracing/instrumentation/expo-router.mdx | 180 +++++++++++++----- 3 files changed, 136 insertions(+), 52 deletions(-) diff --git a/docs/platforms/react-native/integrations/error-boundary.mdx b/docs/platforms/react-native/integrations/error-boundary.mdx index 10accf0218072..a96bf4e740004 100644 --- a/docs/platforms/react-native/integrations/error-boundary.mdx +++ b/docs/platforms/react-native/integrations/error-boundary.mdx @@ -10,6 +10,12 @@ og_image: /og-images/platforms-react-native-integrations-error-boundary.png The React Native SDK exports an error boundary component that uses [React component APIs](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) to automatically catch and send JavaScript errors from inside a React component tree to Sentry, and render a fallback UI. + + +Expo Router has its own per-route `ErrorBoundary` export convention that this component does not interact with. To capture errors that hit Expo Router's boundary, see [Capturing Errors From Expo Router's `ErrorBoundary`](/platforms/react-native/tracing/instrumentation/expo-router/#capturing-errors-from-expo-routers-errorboundary). + + + React error boundaries **only catch errors during rendering, in lifecycle methods, and in constructors**. They do **not** catch errors in event handlers, asynchronous code (`setTimeout`, `Promise`), or native errors. For a fallback UI that covers those cases too, use [`Sentry.GlobalErrorBoundary`](#showing-a-fallback-ui-for-fatal-errors). diff --git a/docs/platforms/react-native/manual-setup/expo.mdx b/docs/platforms/react-native/manual-setup/expo.mdx index 07f7e2d9d5e78..c72ab6955c424 100644 --- a/docs/platforms/react-native/manual-setup/expo.mdx +++ b/docs/platforms/react-native/manual-setup/expo.mdx @@ -160,7 +160,7 @@ To verify that everything is working as expected, build the `Release` version of ### Performance -- [Expo Router tracing](/platforms/react-native/tracing/instrumentation/expo-router/) — Navigation transitions, performance spans, and prefetch instrumentation +- [Expo Router tracing](/platforms/react-native/tracing/instrumentation/expo-router/) — Navigation transitions, performance spans, prefetch instrumentation, and per-route `ErrorBoundary` capture - [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/tracing/instrumentation/expo-router.mdx b/docs/platforms/react-native/tracing/instrumentation/expo-router.mdx index aee83eb1a7f5c..95e0aa9afa87a 100644 --- a/docs/platforms/react-native/tracing/instrumentation/expo-router.mdx +++ b/docs/platforms/react-native/tracing/instrumentation/expo-router.mdx @@ -4,82 +4,73 @@ description: "Learn how to use Sentry's Expo Router instrumentation." sidebar_order: 65 --- -Sentry's React Native SDK package ships with instrumentation for Expo Router. This allows you to see the performance of your navigation transitions and the errors that occur during them. This page will guide you through setting up the instrumentation and configuring it to your needs. +Sentry's React Native SDK ships first-class instrumentation for [Expo Router](https://docs.expo.dev/router/introduction/): navigation transactions with route context, prefetch and method spans, and per-route render-error capture. This page walks through the canonical setup and the surfaces it covers. ## Initialization -The code snippet below shows how to initialize the instrumentation. +Add `expoRouterIntegration` to your `Sentry.init` integrations. No `useNavigationContainerRef` wiring is required — the integration reads Expo Router's internal navigation ref for you. -```javascript {6-8, 15-16, 20-25} {filename: app/_layout.tsx} -import React from 'react'; -import { Slot, useNavigationContainerRef } from 'expo-router'; +```javascript {filename: app/_layout.tsx} {7} import { isRunningInExpoGo } from 'expo'; import * as Sentry from '@sentry/react-native'; -const navigationIntegration = Sentry.reactNavigationIntegration({ - enableTimeToInitialDisplay: !isRunningInExpoGo(), -}); - Sentry.init({ dsn: '___PUBLIC_DSN___', - // Set tracesSampleRate to 1.0 to capture 100% of transactions for tracing. - // We recommend adjusting this value in production. - // Learn more at - // https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate tracesSampleRate: 1.0, - integrations: [navigationIntegration], + integrations: [Sentry.expoRouterIntegration({ + enableTimeToInitialDisplay: !isRunningInExpoGo(), + })], enableNativeFramesTracking: !isRunningInExpoGo(), }); +``` -function RootLayout() { - const ref = useNavigationContainerRef(); - React.useEffect(() => { - if (ref) { - navigationIntegration.registerNavigationContainer(ref); - } - }, [ref]); +That's the whole setup. You don't need to add `reactNavigationIntegration` separately or call `registerNavigationContainer` — `expoRouterIntegration` resolves Expo Router's navigation container and configures route reporting on your behalf. If you already use `reactNavigationIntegration` directly, `expoRouterIntegration` will reuse it. - return ; -} + -export default Sentry.wrap(RootLayout); -``` +`expoRouterIntegration` requires Expo Router to be installed in your project. On non-Expo-Router projects it no-ops cleanly. + + ## Options -You can configure the instrumentation by passing an options object to the constructor: +`expoRouterIntegration` accepts the same options as [`reactNavigationIntegration`](/platforms/react-native/tracing/instrumentation/react-navigation/#options) and forwards them through. The most common ones: -```javascript -Sentry.reactNavigationIntegration({ - enableTimeToInitialDisplay: true, // default: false - routeChangeTimeoutMs: 1000, // default: 1000 - ignoreEmptyBackNavigationTransactions: true, // default: true -}); -``` +### `enableTimeToInitialDisplay` -### routeChangeTimeoutMs +Enables automatic [Time to Initial Display](/platforms/react-native/tracing/instrumentation/time-to-display) measurement for each navigation. Not supported in Expo Go. Default: `false`. -This option specifies how long the instrumentation will wait for the route to mount after a change has been initiated before the transaction is discarded. The default value is `1_000`. +### `routeChangeTimeoutMs` -### enableTimeToInitialDisplay +How long the instrumentation waits for the destination route to mount before discarding the navigation transaction. Default: `1000`. -This option will enable automatic measuring of the time to initial display for each route. To learn more see [Time to Initial Display](/platforms/react-native/tracing/instrumentation/time-to-display). The default value is `false`. +### `ignoreEmptyBackNavigationTransactions` -### ignoreEmptyBackNavigationTransactions +Drops back-navigation transactions that have no spans, which removes a lot of empty-transaction clutter in Sentry. Default: `true`. -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`. +### `enablePrefetchTracking` -## Prefetch Instrumentation +Creates a separate span for `PRELOAD` actions so you can see prefetch timing alongside navigation. Especially useful with Expo Router's `router.prefetch()`. Default: `false`. - +## Route and Parameter Attributes -`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. +The integration attaches a structured representation of the active route to every navigation transaction: - +| Attribute | Example | PII-gated | +|-----------------------|----------------------|-----------| +| `route.name` | `/users/[id]` | No — templated, structural | +| `route.path` | `/users/42` | Yes — concrete, may contain identifiers | +| `route.params` | `{ id: '42' }` | Yes | + +`route.name` is built from Expo Router's `segments`, with grouping segments (e.g. `(tabs)`, `(auth)`) stripped so it matches what users see in the URL bar. It's always safe to send. + +`route.path` and `route.params` may contain user identifiers, so they're sent **only when [`sendDefaultPii`](/platforms/react-native/configuration/options/#sendDefaultPii) is `true`**. Without `sendDefaultPii`, only the templated `route.name` is attached, so navigations are still groupable in Sentry without leaking concrete IDs. + +## Wrapped Router Methods -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. +`Sentry.wrapExpoRouter` instruments the imperative router methods returned by `useRouter()`. Each wrapped call emits a navigation breadcrumb, opens a short-lived span around the dispatch, and tags the next idle navigation span with the initiating method so the navigation transaction can be attributed back to the call site. -```javascript {filename:app/(tabs)/index.tsx} +```javascript {filename: app/(tabs)/index.tsx} import { useRouter } from 'expo-router'; import * as Sentry from '@sentry/react-native'; @@ -87,16 +78,103 @@ function HomeScreen() { const router = Sentry.wrapExpoRouter(useRouter()); return ( -