Skip to content

Commit d66dcb3

Browse files
authored
docs(react-router): Add docs for distributed tracing (#13467)
1 parent f0cdae5 commit d66dcb3

File tree

1 file changed

+127
-57
lines changed
  • docs/platforms/javascript/guides/react-router

1 file changed

+127
-57
lines changed

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

Lines changed: 127 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -83,42 +83,42 @@ npx react-router reveal
8383

8484
Initialize the Sentry React SDK in your `entry.client.tsx` file:
8585

86-
```tsx {filename: entry.client.tsx}
87-
import * as Sentry from "@sentry/react-router";
88-
import { startTransition, StrictMode } from "react";
89-
import { hydrateRoot } from "react-dom/client";
90-
import { HydratedRouter } from "react-router/dom";
91-
92-
Sentry.init({
93-
dsn: "___PUBLIC_DSN___",
94-
95-
// Adds request headers and IP for users, for more info visit:
96-
// https://docs.sentry.io/platforms/javascript/guides/react-router/configuration/options/#sendDefaultPii
97-
sendDefaultPii: true,
98-
99-
integrations: [
100-
// ___PRODUCT_OPTION_START___ performance
101-
Sentry.browserTracingIntegration(),
102-
// ___PRODUCT_OPTION_END___ performance
103-
// ___PRODUCT_OPTION_START___ session-replay
104-
Sentry.replayIntegration(),
105-
// ___PRODUCT_OPTION_END___ session-replay
106-
],
107-
// ___PRODUCT_OPTION_START___ performance
108-
109-
tracesSampleRate: 1.0, // Capture 100% of the transactions
110-
111-
// Set `tracePropagationTargets` to declare which URL(s) should have trace propagation enabled
112-
tracePropagationTargets: [/^\//, /^https:\/\/yourserver\.io\/api/],
113-
// ___PRODUCT_OPTION_END___ performance
114-
// ___PRODUCT_OPTION_START___ session-replay
115-
116-
// Capture Replay for 10% of all sessions,
117-
// plus 100% of sessions with an error
118-
replaysSessionSampleRate: 0.1,
119-
replaysOnErrorSampleRate: 1.0,
120-
// ___PRODUCT_OPTION_END___ session-replay
121-
});
86+
```tsx {diff} {filename: entry.client.tsx}
87+
+import * as Sentry from "@sentry/react-router";
88+
import { startTransition, StrictMode } from "react";
89+
import { hydrateRoot } from "react-dom/client";
90+
import { HydratedRouter } from "react-router/dom";
91+
92+
+Sentry.init({
93+
+ dsn: "___PUBLIC_DSN___",
94+
+
95+
+ // Adds request headers and IP for users, for more info visit:
96+
+ // https://docs.sentry.io/platforms/javascript/guides/react-router/configuration/options/#sendDefaultPii
97+
+ sendDefaultPii: true,
98+
+
99+
+ integrations: [
100+
+ // ___PRODUCT_OPTION_START___ performance
101+
+ Sentry.browserTracingIntegration(),
102+
+ // ___PRODUCT_OPTION_END___ performance
103+
+ // ___PRODUCT_OPTION_START___ session-replay
104+
+ Sentry.replayIntegration(),
105+
+ // ___PRODUCT_OPTION_END___ session-replay
106+
+ ],
107+
+ // ___PRODUCT_OPTION_START___ performance
108+
+
109+
+ tracesSampleRate: 1.0, // Capture 100% of the transactions
110+
+
111+
+ // Set `tracePropagationTargets` to declare which URL(s) should have trace propagation enabled
112+
+ tracePropagationTargets: [/^\//, /^https:\/\/yourserver\.io\/api/],
113+
+ // ___PRODUCT_OPTION_END___ performance
114+
+ // ___PRODUCT_OPTION_START___ session-replay
115+
+
116+
+ // Capture Replay for 10% of all sessions,
117+
+ // plus 100% of sessions with an error
118+
+ replaysSessionSampleRate: 0.1,
119+
+ replaysOnErrorSampleRate: 1.0,
120+
+ // ___PRODUCT_OPTION_END___ session-replay
121+
+});
122122

123123
startTransition(() => {
124124
hydrateRoot(
@@ -133,7 +133,7 @@ startTransition(() => {
133133
Now, update your `app/root.tsx` file to report any unhandled errors from your error boundary:
134134

135135
```tsx {diff} {filename: app/root.tsx}
136-
import * as Sentry from "@sentry/react-router";
136+
+import * as Sentry from "@sentry/react-router";
137137

138138
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
139139
let message = "Oops!";
@@ -199,40 +199,110 @@ Sentry.init({
199199
});
200200
```
201201

202-
In your `entry.server.tsx` file, export the `handleError` function:
202+
Update your `entry.server.tsx` file:
203203

204204
```tsx {diff} {filename: entry.server.tsx}
205-
import * as Sentry from "@sentry/react-router";
206-
import { type HandleErrorFunction } from "react-router";
205+
+import * as Sentry from '@sentry/react-router';
206+
import { createReadableStreamFromReadable } from '@react-router/node';
207+
import { renderToPipeableStream } from 'react-dom/server';
208+
import { ServerRouter } from 'react-router';
209+
import { type HandleErrorFunction } from 'react-router';
210+
211+
+const handleRequest = Sentry.createSentryHandleRequest({
212+
+ ServerRouter,
213+
+ renderToPipeableStream,
214+
+ createReadableStreamFromReadable,
215+
+});
216+
217+
export default handleRequest;
207218

208219
export const handleError: HandleErrorFunction = (error, { request }) => {
209-
// React Router may abort some interrupted requests, report those
220+
// React Router may abort some interrupted requests, don't log those
210221
if (!request.signal.aborted) {
211222
+ Sentry.captureException(error);
212-
213-
// make sure to still log the error so you can see it
223+
// optionally log the error so you can see it
214224
console.error(error);
215225
}
216226
};
217227

218-
- export default function handleRequest(
219-
+ function handleRequest(
220-
request: Request,
221-
responseStatusCode: number,
222-
responseHeaders: Headers,
223-
routerContext: EntryContext,
224-
loadContext: AppLoadContext,
225-
) {
226-
return new Promise((resolve, reject) => {
227-
// ...
228-
}
229-
}
230-
231-
+ export default Sentry.sentryHandleRequest(handleRequest);
232228

233229
// ... rest of your server entry
234230
```
235231

232+
<Expandable title="Do you need to customize your handleRequest function?">
233+
If you need to update the logic of your `handleRequest` function you'll need to include the provided Sentry helper functions (`getMetaTagTransformer` and `wrapSentryHandleRequest`) manually:
234+
235+
```tsx {1-4, 44-45, 69-70}
236+
import { getMetaTagTransformer, wrapSentryHandleRequest } from '@sentry/react-router';
237+
// ... other imports
238+
239+
const handleRequest = function handleRequest(
240+
request: Request,
241+
responseStatusCode: number,
242+
responseHeaders: Headers,
243+
routerContext: EntryContext,
244+
_loadContext: AppLoadContext,
245+
): Promise<Response> {
246+
return new Promise((resolve, reject) => {
247+
let shellRendered = false;
248+
const userAgent = request.headers.get('user-agent');
249+
250+
// Determine if we should use onAllReady or onShellReady
251+
const isBot = typeof userAgent === 'string' && botRegex.test(userAgent);
252+
const isSpaMode = !!(routerContext as { isSpaMode?: boolean }).isSpaMode;
253+
254+
const readyOption = isBot || isSpaMode ? 'onAllReady' : 'onShellReady';
255+
256+
const { pipe, abort } = renderToPipeableStream(
257+
<ServerRouter context={routerContext} url={request.url} />,
258+
{
259+
[readyOption]() {
260+
shellRendered = true;
261+
const body = new PassThrough();
262+
263+
const stream = createReadableStreamFromReadable(body);
264+
265+
responseHeaders.set('Content-Type', 'text/html');
266+
267+
resolve(
268+
new Response(stream, {
269+
headers: responseHeaders,
270+
status: responseStatusCode,
271+
}),
272+
);
273+
274+
// this enables distributed tracing between client and server
275+
pipe(getMetaTagTransformer(body));
276+
},
277+
onShellError(error: unknown) {
278+
reject(error);
279+
},
280+
onError(error: unknown) {
281+
// eslint-disable-next-line no-param-reassign
282+
responseStatusCode = 500;
283+
// Log streaming rendering errors from inside the shell. Don't log
284+
// errors encountered during initial shell rendering since they'll
285+
// reject and get logged in handleDocumentRequest.
286+
if (shellRendered) {
287+
// eslint-disable-next-line no-console
288+
console.error(error);
289+
}
290+
},
291+
},
292+
);
293+
294+
// Abort the rendering stream after the `streamTimeout`
295+
setTimeout(abort, streamTimeout);
296+
});
297+
};
298+
299+
// wrap the default export
300+
export default wrapSentryHandleRequest(handleRequest);
301+
302+
// ... rest of your entry.server.ts file
303+
```
304+
</Expandable>
305+
236306
### Update Scripts
237307

238308
Since React Router is running in ESM mode, you need to use the `--import` command line options to load our server-side instrumentation module before the application starts.

0 commit comments

Comments
 (0)