Skip to content

Commit 3c7aa9b

Browse files
authored
feat(react-router): Add Instrumentation API guide (#16171)
<!-- Use this checklist to make sure your PR is ready for merge. You may delete any sections you don't need. --> ## DESCRIBE YOUR PR Documents: getsentry/sentry-javascript#18580 ## IS YOUR CHANGE URGENT? Help us prioritize incoming PRs by letting us know when the change needs to go live. - [ ] Urgent deadline (GA date, etc.): <!-- ENTER DATE HERE --> - [ ] Other deadline: <!-- ENTER DATE HERE --> - [ ] None: Not urgent, can wait up to 1 week+ ## SLA - Teamwork makes the dream work, so please add a reviewer to your PRs. - Please give the docs team up to 1 week to review your PR unless you've added an urgent due date to it. Thanks in advance for your help! ## PRE-MERGE CHECKLIST *Make sure you've checked the following before merging your changes:* - [x] Checked Vercel preview for correctness, including links - [ ] PR was reviewed and approved by any necessary SMEs (subject matter experts) - [ ] PR was reviewed and approved by a member of the [Sentry docs team](https://github.com/orgs/getsentry/teams/docs) ## EXTRA RESOURCES - [Sentry Docs contributor guide](https://docs.sentry.io/contributing/)
1 parent 107d065 commit 3c7aa9b

File tree

4 files changed

+245
-4
lines changed

4 files changed

+245
-4
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
title: React Router Features
3+
description: "Learn how Sentry's React Router SDK exposes features for first class integration with the framework."
4+
sidebar_order: 4
5+
---
6+
7+
<Alert level="warning">
8+
9+
This SDK is currently in **beta**. Beta features are still in progress and may have bugs. Please reach out on
10+
[GitHub](https://github.com/getsentry/sentry-javascript/issues/new/choose) if you have any feedback or concerns.
11+
12+
</Alert>
13+
14+
The Sentry React Router SDK offers React Router-specific features for first class integration with the framework.
15+
16+
<PageGrid />
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
---
2+
title: Instrumentation API
3+
description: "Automatic tracing for loaders, actions, middleware, navigations, fetchers, lazy routes, and request handlers using React Router's instrumentation API."
4+
sidebar_order: 10
5+
---
6+
7+
<Alert level="warning" title="Experimental">
8+
9+
React Router's instrumentation API is experimental and uses the `unstable_instrumentations` name. The `unstable_` prefix indicates the API may change in minor releases.
10+
11+
</Alert>
12+
13+
React Router 7.9.5+ provides an [instrumentation API](https://reactrouter.com/how-to/instrumentation) that enables automatic span creation for loaders, actions, middleware, navigations, fetchers, lazy routes, and request handlers without the need for manual wrapper functions. Transaction names (for HTTP requests, pageloads, and navigations) use parameterized route patterns, such as `/users/:id`, and errors are automatically captured with proper context.
14+
15+
## Server-Side Setup
16+
17+
<SplitLayout>
18+
<SplitSection>
19+
<SplitSectionText>
20+
21+
Export `unstable_instrumentations` from your `entry.server.tsx` to enable automatic server-side tracing.
22+
23+
The `createSentryServerInstrumentation()` function creates spans for:
24+
25+
- Request handlers (root HTTP server spans)
26+
- Loaders
27+
- Actions
28+
- Middleware
29+
- Lazy route loading
30+
31+
</SplitSectionText>
32+
<SplitSectionCode>
33+
34+
```tsx {tabTitle:Server} {filename:entry.server.tsx}
35+
import * as Sentry from "@sentry/react-router";
36+
import { createReadableStreamFromReadable } from "@react-router/node";
37+
import { renderToPipeableStream } from "react-dom/server";
38+
import { ServerRouter } from "react-router";
39+
40+
export default Sentry.createSentryHandleRequest({
41+
ServerRouter,
42+
renderToPipeableStream,
43+
createReadableStreamFromReadable,
44+
});
45+
46+
export const handleError = Sentry.createSentryHandleError();
47+
48+
// Enable automatic server-side instrumentation
49+
export const unstable_instrumentations = [
50+
Sentry.createSentryServerInstrumentation(),
51+
];
52+
```
53+
54+
</SplitSectionCode>
55+
</SplitSection>
56+
57+
<SplitSection>
58+
<SplitSectionText>
59+
60+
You can optionally configure error capture behavior:
61+
62+
</SplitSectionText>
63+
<SplitSectionCode>
64+
65+
```typescript
66+
Sentry.createSentryServerInstrumentation({
67+
// Capture errors from loaders/actions automatically (default: true)
68+
captureErrors: true,
69+
});
70+
```
71+
72+
</SplitSectionCode>
73+
</SplitSection>
74+
</SplitLayout>
75+
76+
## Client-Side Setup
77+
78+
<SplitLayout>
79+
<SplitSection>
80+
<SplitSectionText>
81+
82+
To enable the client-side instrumentation API, pass `useInstrumentationAPI: true` to `reactRouterTracingIntegration()` and provide the `clientInstrumentation` to `HydratedRouter`.
83+
84+
The client instrumentation creates spans for:
85+
86+
- Navigations (including back/forward)
87+
- Fetchers
88+
- Client loaders
89+
- Client actions
90+
- Client middleware
91+
- Lazy route loading
92+
93+
<Alert level="info" title="Framework Mode limitation">
94+
95+
`HydratedRouter` doesn't currently invoke client-side instrumentation hooks when running in Framework Mode. As a result, only client-side navigation spans are captured through the SDK's built-in instrumentation. The client-side setup shown here prepares your app for when React Router adds support for invoking these hooks.
96+
97+
</Alert>
98+
99+
</SplitSectionText>
100+
<SplitSectionCode>
101+
102+
```tsx {tabTitle:Client} {filename:entry.client.tsx}
103+
import * as Sentry from "@sentry/react-router";
104+
import { startTransition, StrictMode } from "react";
105+
import { hydrateRoot } from "react-dom/client";
106+
import { HydratedRouter } from "react-router/dom";
107+
108+
const tracing = Sentry.reactRouterTracingIntegration({
109+
useInstrumentationAPI: true,
110+
});
111+
112+
Sentry.init({
113+
dsn: "___PUBLIC_DSN___",
114+
integrations: [tracing],
115+
tracesSampleRate: 1.0,
116+
});
117+
118+
startTransition(() => {
119+
hydrateRoot(
120+
document,
121+
<StrictMode>
122+
<HydratedRouter
123+
unstable_instrumentations={[tracing.clientInstrumentation]}
124+
/>
125+
</StrictMode>
126+
);
127+
});
128+
```
129+
130+
</SplitSectionCode>
131+
</SplitSection>
132+
</SplitLayout>
133+
134+
## Migrating from Manual Wrappers
135+
136+
If you're using `wrapServerLoader` and `wrapServerAction`, you can migrate to the instrumentation API. The SDK automatically detects when the instrumentation API is active and skips span creation in manual wrappers, so you can migrate incrementally without duplicate spans.
137+
138+
<SplitLayout>
139+
<SplitSection>
140+
<SplitSectionText>
141+
142+
**Before migrating** (manual wrappers):
143+
144+
Without the instrumentation API, each loader and action needs to be wrapped individually.
145+
146+
</SplitSectionText>
147+
<SplitSectionCode>
148+
149+
```tsx {filename:app/routes/users.$id.tsx}
150+
import * as Sentry from "@sentry/react-router";
151+
152+
export const loader = Sentry.wrapServerLoader(
153+
{ name: "Load User" },
154+
async ({ params }) => {
155+
const user = await getUser(params.id);
156+
return { user };
157+
}
158+
);
159+
160+
export const action = Sentry.wrapServerAction(
161+
{ name: "Update User" },
162+
async ({ request }) => {
163+
const formData = await request.formData();
164+
return updateUser(formData);
165+
}
166+
);
167+
```
168+
169+
</SplitSectionCode>
170+
</SplitSection>
171+
172+
<SplitSection>
173+
<SplitSectionText>
174+
175+
**After migrating** (instrumentation API):
176+
177+
After adding the instrumentation export once in `entry.server.tsx`, all loaders and actions are traced automatically.
178+
179+
</SplitSectionText>
180+
<SplitSectionCode>
181+
182+
```tsx {filename:app/routes/users.$id.tsx}
183+
// No Sentry imports or wrappers needed
184+
185+
export async function loader({ params }) {
186+
const user = await getUser(params.id);
187+
return { user };
188+
}
189+
190+
export async function action({ request }) {
191+
const formData = await request.formData();
192+
return updateUser(formData);
193+
}
194+
```
195+
196+
</SplitSectionCode>
197+
</SplitSection>
198+
</SplitLayout>
199+
200+
## Troubleshooting
201+
202+
<Expandable title="Spans not appearing for loaders/actions">
203+
204+
If you're not seeing spans for your loaders and actions:
205+
206+
1. Check that the React Router version is 7.9.5 or later
207+
2. Make sure `unstable_instrumentations` is exported from `entry.server.tsx`
208+
3. Verify `tracesSampleRate` is set in your server configuration
209+
210+
</Expandable>
211+
212+
<Expandable title="Duplicate spans when using manual wrappers">
213+
214+
If you're seeing duplicate spans after adding the instrumentation API:
215+
216+
The SDK automatically detects when the instrumentation API is active and skips span creation in manual wrappers. If you're still seeing duplicates:
217+
218+
1. Update to the latest SDK version
219+
2. Check that the instrumentation export is correctly configured
220+
3. Enable debug mode to verify the manual wrappers are being skipped—they log a message when the instrumentation API is active
221+
222+
</Expandable>

docs/platforms/javascript/guides/react-router/index.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ Sentry.init({
116116
// ___PRODUCT_OPTION_START___ performance
117117
// Registers and configures the Tracing integration,
118118
// which automatically instruments your application to monitor its
119-
// performance, including custom Angular routing instrumentation
119+
// performance, including custom React Router routing instrumentation
120120
Sentry.reactRouterTracingIntegration(),
121121
// ___PRODUCT_OPTION_END___ performance
122122
// ___PRODUCT_OPTION_START___ session-replay

docs/platforms/javascript/guides/react-router/manual-setup.mdx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ Initialize Sentry in your `entry.client.tsx` file:
104104
+ // ___PRODUCT_OPTION_START___ performance
105105
+ // Registers and configures the Tracing integration,
106106
+ // which automatically instruments your application to monitor its
107-
+ // performance, including custom Angular routing instrumentation
107+
+ // performance, including custom React Router routing instrumentation
108108
+ Sentry.reactRouterTracingIntegration(),
109109
+ // ___PRODUCT_OPTION_END___ performance
110110
+ // ___PRODUCT_OPTION_START___ session-replay
@@ -131,7 +131,7 @@ Initialize Sentry in your `entry.client.tsx` file:
131131
+ // We recommend adjusting this value in production
132132
+ // Learn more at
133133
+ // https://docs.sentry.io/platforms/javascript/guides/react-router/configuration/options/#traces-sample-rate
134-
+ tracesSampleRate: 1.0, // Capture 100% of the transactions
134+
+ tracesSampleRate: 1.0,
135135
+
136136
+ // Set `tracePropagationTargets` to declare which URL(s) should have trace propagation enabled
137137
+ tracePropagationTargets: [/^\//, /^https:\/\/yourserver\.io\/api/],
@@ -206,7 +206,10 @@ export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
206206
- **Node 20:** Version \<20.19
207207
- **Node 22:** Version \<22.12
208208

209-
If you're on a different version, use our manual server wrappers.
209+
If you're on a different version, you have two options:
210+
211+
1. **Recommended**: Use the <PlatformLink to="/features/instrumentation-api/">Instrumentation API</PlatformLink> (React Router 7.9.5+) for automatic tracing without Node version restrictions
212+
2. **Alternative**: Use our manual server wrappers (shown below)
210213

211214
For server loaders use `wrapServerLoader`:
212215

0 commit comments

Comments
 (0)