# Instrumentation API | Sentry for React Router Framework

##### 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](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.

## [Server-Side Setup](https://docs.sentry.io/platforms/javascript/guides/react-router/features/instrumentation-api.md#server-side-setup)

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.tsx`

```tsx
import * 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:

```typescript
Sentry.createSentryServerInstrumentation({
  // Capture errors from loaders/actions automatically (default: true)
  captureErrors: true,
});
```

## [Client-Side Setup](https://docs.sentry.io/platforms/javascript/guides/react-router/features/instrumentation-api.md#client-side-setup)

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.tsx`

```tsx
import * 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>,
  );
});
```

## [Migrating from Manual Wrappers](https://docs.sentry.io/platforms/javascript/guides/react-router/features/instrumentation-api.md#migrating-from-manual-wrappers)

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.tsx`

```tsx
import * 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`

```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);
}
```

## [Troubleshooting](https://docs.sentry.io/platforms/javascript/guides/react-router/features/instrumentation-api.md#troubleshooting)

Spans not appearing for loaders/actions

If you're not seeing spans for your loaders and actions:

1. Check that the React Router version is 7.9.5 or later
2. Make sure `unstable_instrumentations` is exported from `entry.server.tsx`
3. Verify `tracesSampleRate` is set in your server configuration

Duplicate spans when using manual wrappers

If you're seeing duplicate spans after adding the instrumentation API:

The SDK automatically detects when the instrumentation API is active and skips span creation in manual wrappers. If you're still seeing duplicates:

1. Update to the latest SDK version
2. Check that the instrumentation export is correctly configured
3. Enable debug mode to verify the manual wrappers are being skipped—they log a message when the instrumentation API is active
