Instrumentation API
Automatic tracing for loaders, actions, middleware, navigations, fetchers, lazy routes, and request handlers using React Router's instrumentation API.
Experimental
React Router's instrumentation API is experimental and uses the unstable_instrumentations name. The unstable_ prefix indicates the API may change in minor releases.
React Router 7.9.5+ provides an instrumentation API 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.
Export unstable_instrumentations from your entry.server.tsx to enable automatic server-side tracing.
The createSentryServerInstrumentation() function creates spans for:
- Request handlers (root HTTP server spans)
- Loaders
- Actions
- Middleware
- Lazy route loading
entry.server.tsximport * as Sentry from "@sentry/react-router";
import { createReadableStreamFromReadable } from "@react-router/node";
import { renderToPipeableStream } from "react-dom/server";
import { ServerRouter } from "react-router";
export default Sentry.createSentryHandleRequest({
ServerRouter,
renderToPipeableStream,
createReadableStreamFromReadable,
});
export const handleError = Sentry.createSentryHandleError();
// Enable automatic server-side instrumentation
export const unstable_instrumentations = [
Sentry.createSentryServerInstrumentation(),
];
You can optionally configure error capture behavior:
Sentry.createSentryServerInstrumentation({
// Capture errors from loaders/actions automatically (default: true)
captureErrors: true,
});
To enable the client-side instrumentation API, pass useInstrumentationAPI: true to reactRouterTracingIntegration() and provide the clientInstrumentation to HydratedRouter.
The client instrumentation creates spans for:
- Navigations (including back/forward)
- Fetchers
- Client loaders
- Client actions
- Client middleware
- Lazy route loading
Framework Mode limitation
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.
entry.client.tsximport * as Sentry from "@sentry/react-router";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
import { HydratedRouter } from "react-router/dom";
const tracing = Sentry.reactRouterTracingIntegration({
useInstrumentationAPI: true,
});
Sentry.init({
dsn: "___PUBLIC_DSN___",
integrations: [tracing],
tracesSampleRate: 1.0,
});
startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<HydratedRouter
unstable_instrumentations={[tracing.clientInstrumentation]}
/>
</StrictMode>,
);
});
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.
Before migrating (manual wrappers):
Without the instrumentation API, each loader and action needs to be wrapped individually.
app/routes/users.$id.tsximport * as Sentry from "@sentry/react-router";
export const loader = Sentry.wrapServerLoader(
{ name: "Load User" },
async ({ params }) => {
const user = await getUser(params.id);
return { user };
},
);
export const action = Sentry.wrapServerAction(
{ name: "Update User" },
async ({ request }) => {
const formData = await request.formData();
return updateUser(formData);
},
);
After migrating (instrumentation API):
After adding the instrumentation export once in entry.server.tsx, all loaders and actions are traced automatically.
app/routes/users.$id.tsx// No Sentry imports or wrappers needed
export async function loader({ params }) {
const user = await getUser(params.id);
return { user };
}
export async function action({ request }) {
const formData = await request.formData();
return updateUser(formData);
}
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").