All skills
getsentry avatar

/sentry-react-sdk

@98a20d1

Full Sentry SDK setup for React. Use when asked to "add Sentry to React", "install @sentry/react", or configure error monitoring, tracing, session replay, profiling, or logging for React applications. Supports React 16+, React Router v5-v7, TanStack Router, Redux, Vite, and webpack.

Use this Skill: https://skilld.dev/gh/getsentry/sentry-agent-skills/sentry-react-sdk

This session only. Nothing lands on disk.

referencestracing.md

≈11k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Tracing — Sentry React SDK

Minimum SDK: @sentry/react ≥8.0.0+
reactRouterV7BrowserTracingIntegration: requires @sentry/react ≥8.0.0
ignoreSpans: requires @sentry/react ≥10.2.0
enableAsyncRouteHandlers + lazyRouteManifest: requires @sentry/react ≥10.39.0
enableLongAnimationFrame: requires @sentry/react ≥8.18.0


How Automatic Tracing Works

What's traced Op How
Initial page load pageload browserTracingIntegration() reads window.performance timing
Client-side navigations navigation History API (pushState / replaceState)
fetch() requests http.client Patched automatically
XMLHttpRequest requests http.client Patched automatically
Long Tasks (main-thread blocks > 50ms) ui.long-task PerformanceLongTaskTiming observer
Long Animation Frames (≥8.18.0) ui.long-animation-frame PerformanceLongAnimationFrameTiming observer
INP interactions ui.interaction PerformanceEventTiming observer, emitted on page hide

Core Setup

// src/instrument.ts (imported FIRST in main.tsx / index.tsx)
import * as Sentry from "@sentry/react";

Sentry.init({
  dsn: import.meta.env.VITE_SENTRY_DSN,
  environment: import.meta.env.MODE,

  integrations: [
    Sentry.browserTracingIntegration(),
  ],

  // Tracing sample rates
  tracesSampleRate: 1.0,   // 100% in dev; lower to 0.1–0.2 in production

  // Which outgoing requests get sentry-trace + baggage headers
  tracePropagationTargets: [
    "localhost",
    /^https:\/\/api\.yourapp\.com/,
  ],
});

To disable tracing entirely: omit both tracesSampleRate and tracesSampler. Setting tracesSampleRate: 0 is not the same — the integration still runs, it just doesn't send data.


browserTracingIntegration — All Options

Sentry.browserTracingIntegration({
  /* option: default */
})

Page Load & Navigation

Option Type Default Description
instrumentPageLoad boolean true Create a pageload span on initial load. Disable when you want to name the span yourself via startBrowserTracingPageLoadSpan.
instrumentNavigation boolean true Create navigation spans on History API changes.

Span Lifecycle / Timing

Option Type Default Description
idleTimeout number (ms) 1000 How long to wait after the last child span finishes before closing the root span. The root takes the last child's end time as its own end time.
finalTimeout number (ms) 30000 Hard cap on how long a pageload/navigation span can live. Prevents runaway open spans.
childSpanTimeout number (ms) 15000 If a child span hasn't finished within this time, the root span finishes anyway.
markBackgroundSpan boolean true When the tab goes to the background, mark the active span as cancelled and close it.

HTTP Request Spans

Option Type Default Description
traceFetch boolean true Auto-create child spans for fetch() calls.
traceXHR boolean true Auto-create child spans for XMLHttpRequest calls.
enableHTTPTimings boolean true Enrich HTTP spans with Resource Timing API data: DNS lookup, TLS handshake, connection, TTFB, download time.
shouldCreateSpanForRequest (url: string) => boolean — Return false to skip creating a span for a specific URL.
onRequestSpanStart (span, requestInfo) => void — Fires when a fetch/XHR span starts. Add custom attributes based on headers or URL.

Performance Observations

Option Type Default Description
enableLongTask boolean true Capture spans for Long Tasks — main-thread blocks > 50ms.
enableLongAnimationFrame boolean true Capture Long Animation Frames (LoAF). Supersedes Long Tasks for most use cases. SDK ≥8.18.0.
enableInp boolean true (SDK 8.x+) Auto-capture INP events as standalone spans. In SDK 7.x, defaults to false and must be opted in.
interactionsSampleRate number 1.0 Applied on top of tracesSampleRate for INP spans. interactionsSampleRate: 0.5 + tracesSampleRate: 0.1 = 5% of interactions captured.

Span Naming

Option Type Default Description
beforeStartSpan (context: StartSpanOptions) => StartSpanOptions — Called just before every pageload or navigation span is created. Mutate and return context to rename the span, change op, or add attributes. Primary use: parameterize URLs (/users/123 → /users/<id>).
browserTracingIntegration({
  beforeStartSpan: (context) => ({
    ...context,
    name: location.pathname
      .replace(/\/[a-f0-9]{8,}/g, "/<hash>")  // strip hashes/UUIDs
      .replace(/\/\d+/g, "/<id>"),              // strip numeric IDs
  }),
})

Trace Linking

Option Type Default Description
linkPreviousTrace 'in-memory' | 'session-storage' | false 'in-memory' How a new pageload links back to the previous trace. 'session-storage' persists across hard reloads. false disables linking.
enableReportPageLoaded boolean false Enables Sentry.reportPageLoaded() for manually signalling page load completion in complex hydration scenarios.

Span Filtering

Option Type Default Description
ignoreResourceSpans string[] [] Skip resource spans by op prefix. Example: ["resource.css", "resource.script", "resource.img"].
ignorePerformanceApiSpans Array<string | RegExp> [] Skip spans created from performance.mark()/performance.measure() matching these names.

Full Example With All Common Options

import * as Sentry from "@sentry/react";

Sentry.init({
  dsn: import.meta.env.VITE_SENTRY_DSN,
  environment: import.meta.env.MODE,

  integrations: [
    Sentry.browserTracingIntegration({
      // Lifecycle
      idleTimeout: 1000,
      finalTimeout: 30_000,
      childSpanTimeout: 15_000,
      markBackgroundSpan: true,

      // HTTP spans
      traceFetch: true,
      traceXHR: true,
      enableHTTPTimings: true,
      shouldCreateSpanForRequest: (url) =>
        !url.includes("/health") && !url.includes("/__webpack_hmr"),
      onRequestSpanStart: (span, { headers }) => {
        const rid = headers?.["x-request-id"];
        if (rid) span.setAttribute("request.id", rid);
      },

      // Performance observations
      enableLongTask: true,
      enableLongAnimationFrame: true,  // SDK ≥8.18.0
      enableInp: true,
      interactionsSampleRate: 1.0,

      // Span naming
      beforeStartSpan: (context) => ({
        ...context,
        name: context.name.replace(/\/\d+/g, "/<id>"),
      }),

      // Filtering
      ignoreResourceSpans: ["resource.css"],

      // Trace linking
      linkPreviousTrace: "in-memory",
    }),
  ],

  tracesSampleRate: 1.0,
  tracePropagationTargets: ["localhost", /^https:\/\/api\.myapp\.com/],
});

What's Auto-Instrumented

Page Load (op: "pageload")

  • Created on initial page render using window.performance timing API
  • Contains Web Vitals: LCP, CLS, FCP, TTFB
  • HTTP requests made during page load appear as child spans
  • Long Tasks and Long Animation Frames appear as child spans

Navigation (op: "navigation")

  • Created on every client-side navigation via the History API
  • Does not include Web Vitals (those are page-load only)
  • HTTP requests during navigation appear as child spans

HTTP Spans (op: "http.client")

  • Automatic for both fetch() and XMLHttpRequest
  • Captures: method, URL, HTTP status code, response size
  • With enableHTTPTimings: DNS lookup time, TLS handshake, connection time, TTFB, download time

Long Task Spans (op: "ui.long-task")

  • Created for any main-thread block > 50ms
  • Helps identify JavaScript that blocks interactivity

Long Animation Frame Spans (op: "ui.long-animation-frame")

  • SDK 8.18.0+; based on the LoAF API
  • Captures render-blocking work including style/layout recalculations
  • More accurate than Long Tasks for measuring rendering bottlenecks

INP / Interaction Spans (op: "ui.interaction")

  • Standalone spans capturing Interaction to Next Paint
  • Emitted on page hide (tab switch, navigation away)
  • Attributes: component, element, interaction_type
  • On by default in SDK 8.x+; opt-in (enableInp: true) in SDK 7.x

Web Vitals

browserTracingIntegration() captures Core Web Vitals automatically and surfaces them in the Sentry Web Vitals product module:

Vital What it measures Good Needs Improvement Poor
LCP — Largest Contentful Paint Time for largest viewport element to render ≤ 2.5s ≤ 4s > 4s
INP — Interaction to Next Paint Time from user interaction to next paint (replaced FID March 2024) ≤ 200ms ≤ 500ms > 500ms
CLS — Cumulative Layout Shift Sum of unexpected layout shift scores ≤ 0.1 ≤ 0.25 > 0.25
FCP — First Contentful Paint Time for first content to render ≤ 1s ≤ 3s > 3s
TTFB — Time to First Byte Time until browser receives first byte ≤ 100ms ≤ 200ms > 200ms
FID — First Input Delay (Legacy — collected but replaced by INP) ≤ 100ms ≤ 300ms > 300ms

LCP and CLS timing note: These keep changing after the pageload span ends. Sentry captures their final values via visibilitychange and page hide events. INP is similarly emitted as a standalone span on page hide.

INP in SDK 7.x (must opt in):

browserTracingIntegration({ enableInp: true })

React Router Integrations

All React Router integrations live in @sentry/react. The core mechanism: replace browserTracingIntegration() with the router-specific variant. Both cannot be used simultaneously.


React Router v7 (Library Mode)

Package: react-router (v7)
Import source for hooks: "react-router"

Method 1 — createBrowserRouter (Recommended)
// src/instrument.ts
import React from "react";
import {
  createBrowserRouter,
  createRoutesFromChildren,
  matchRoutes,
  useLocation,
  useNavigationType,
} from "react-router";
import * as Sentry from "@sentry/react";

Sentry.init({
  dsn: import.meta.env.VITE_SENTRY_DSN,
  integrations: [
    Sentry.reactRouterV7BrowserTracingIntegration({
      useEffect: React.useEffect,
      useLocation,
      useNavigationType,
      createRoutesFromChildren,
      matchRoutes,
    }),
  ],
  tracesSampleRate: 1.0,
  tracePropagationTargets: ["localhost", /^https:\/\/api\.myapp\.com/],
});
// src/router.ts
import { createBrowserRouter } from "react-router";
import * as Sentry from "@sentry/react";
import { RootLayout, RootErrorBoundary } from "./layouts";
import { HomePage, UsersPage, UserDetailPage, DashboardPage } from "./pages";

// Wrap createBrowserRouter with Sentry instrumentation
const sentryCreateBrowserRouter = Sentry.wrapCreateBrowserRouterV7(createBrowserRouter);

export const router = sentryCreateBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    errorElement: <RootErrorBoundary />,  // see Error Boundary section below
    children: [
      { index: true,                      element: <HomePage /> },
      { path: "users",                    element: <UsersPage /> },
      { path: "users/:userId",            element: <UserDetailPage /> },
      { path: "dashboard",                element: <DashboardPage />,
        children: [
          { path: "analytics",            element: <AnalyticsPage /> },
        ],
      },
    ],
  },
]);
// src/main.tsx
import ReactDOM from "react-dom/client";
import { RouterProvider } from "react-router";
import "./instrument";   // ← MUST be first
import { router } from "./router";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <RouterProvider router={router} />
);

Lazy routes (SDK ≥10.39.0): add enableAsyncRouteHandlers and declare all route paths:

Sentry.reactRouterV7BrowserTracingIntegration({
  useEffect: React.useEffect,
  useLocation,
  useNavigationType,
  createRoutesFromChildren,
  matchRoutes,
  enableAsyncRouteHandlers: true,
  lazyRouteManifest: [
    "/",
    "/users",
    "/users/:userId",
    "/users/:userId/settings",
    "/dashboard",
    "/dashboard/analytics",
  ],
})

Other router factories:

Factory Sentry wrapper
createBrowserRouter Sentry.wrapCreateBrowserRouterV7
createMemoryRouter Sentry.wrapCreateMemoryRouterV7
createHashRouter Sentry.wrapCreateBrowserRouterV7 (works for both)
Method 2 — <Routes> Component
import React from "react";
import ReactDOM from "react-dom/client";
import {
  BrowserRouter, Routes, Route,
  createRoutesFromChildren, matchRoutes,
  useLocation, useNavigationType,
} from "react-router";
import * as Sentry from "@sentry/react";

Sentry.init({
  // ... same init as Method 1
});

// Wrap Routes ONCE at the top level — do NOT wrap nested <Routes>
const SentryRoutes = Sentry.withSentryReactRouterV7Routing(Routes);

function App() {
  return (
    <BrowserRouter>
      <SentryRoutes>
        <Route path="/"               element={<HomePage />} />
        <Route path="/about"          element={<AboutPage />} />
        <Route path="/users/:userId"  element={<UserDetailPage />} />
        <Route path="*"               element={<NotFoundPage />} />
      </SentryRoutes>
    </BrowserRouter>
  );
}

Also works with MemoryRouter and HashRouter.

Method 3 — useRoutes Hook
import { useRoutes, BrowserRouter } from "react-router";
import * as Sentry from "@sentry/react";

// MUST call wrapUseRoutesV7 OUTSIDE any React component
const useSentryRoutes = Sentry.wrapUseRoutesV7(useRoutes);

function App() {
  return useSentryRoutes([
    { path: "/",               element: <HomePage /> },
    { path: "/users/:userId",  element: <UserDetailPage /> },
    { path: "/dashboard",      element: <DashboardPage />,
      children: [
        { path: "analytics",   element: <AnalyticsPage /> },
      ],
    },
  ]);
}

ReactDOM.createRoot(document.getElementById("root")!).render(
  <BrowserRouter><App /></BrowserRouter>
);
Error Boundary (Required for Production Error Capture)

React Router v7's default errorElement swallows errors silently. You must capture them manually:

import { useRouteError } from "react-router";
import * as Sentry from "@sentry/react";

export function SentryRouteErrorBoundary() {
  const error = useRouteError() as Error;

  React.useEffect(() => {
    if (error) Sentry.captureException(error);
  }, [error]);

  return (
    <div role="alert">
      <h1>Something went wrong</h1>
      <p>{error?.message ?? "An unexpected error occurred."}</p>
    </div>
  );
}

// Apply as errorElement on your root route and any nested boundaries:
const router = sentryCreateBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    errorElement: <SentryRouteErrorBoundary />,
    children: [
      {
        path: "checkout",
        element: <CheckoutPage />,
        errorElement: <SentryRouteErrorBoundary />,  // nested boundary
      },
    ],
  },
]);

React Router v6

Package: react-router-dom (v6)
Import source for hooks: "react-router-dom"

Method 1 — createBrowserRouter (Recommended for v6.4+)
import React from "react";
import {
  createBrowserRouter,
  createRoutesFromChildren,
  matchRoutes,
  useLocation,
  useNavigationType,
} from "react-router-dom";
import * as Sentry from "@sentry/react";

Sentry.init({
  dsn: import.meta.env.VITE_SENTRY_DSN,
  integrations: [
    Sentry.reactRouterV6BrowserTracingIntegration({
      useEffect: React.useEffect,
      useLocation,
      useNavigationType,
      createRoutesFromChildren,
      matchRoutes,
    }),
  ],
  tracesSampleRate: 1.0,
});

// Wrap createBrowserRouter
const sentryCreateBrowserRouter =
  Sentry.wrapCreateBrowserRouterV6(createBrowserRouter);

export const router = sentryCreateBrowserRouter([
  {
    path: "/",
    element: <Root />,
    children: [
      { index: true,           element: <HomePage /> },
      { path: "users/:userId", element: <UserDetailPage /> },
      { path: "settings",      element: <SettingsPage /> },
    ],
  },
]);

Other router factories (SDK ≥8.50.0):

Factory Sentry wrapper
createBrowserRouter Sentry.wrapCreateBrowserRouterV6
createMemoryRouter Sentry.wrapCreateMemoryRouterV6
Method 2 — <Routes> Component
import {
  BrowserRouter, Routes, Route,
  createRoutesFromChildren, matchRoutes,
  useLocation, useNavigationType,
} from "react-router-dom";
import * as Sentry from "@sentry/react";

Sentry.init({ /* ... same as above */ });

const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes);

function App() {
  return (
    <BrowserRouter>
      <SentryRoutes>
        <Route path="/"               element={<HomePage />} />
        <Route path="/users/:userId"  element={<UserPage />} />
        <Route path="*"               element={<NotFoundPage />} />
      </SentryRoutes>
    </BrowserRouter>
  );
}
Method 3 — useRoutes Hook
import { useRoutes, BrowserRouter } from "react-router-dom";
import * as Sentry from "@sentry/react";

// Call OUTSIDE components
const useSentryRoutes = Sentry.wrapUseRoutesV6(useRoutes);

function App() {
  return useSentryRoutes([
    { path: "/",              element: <Home /> },
    { path: "/users/:userId", element: <User /> },
  ]);
}

React Router v4 / v5

Package: react-router-dom (v4 or v5) + history

Method 1 — withSentryRouting HOC (Recommended)
import React from "react";
import ReactDOM from "react-dom";
import { Route, Router, Switch } from "react-router-dom";
import { createBrowserHistory } from "history";
import * as Sentry from "@sentry/react";

// 1. Create a history instance
const history = createBrowserHistory();

// 2. Init with reactRouterV5BrowserTracingIntegration
Sentry.init({
  dsn: "...",
  integrations: [
    Sentry.reactRouterV5BrowserTracingIntegration({ history }),
  ],
  tracesSampleRate: 1.0,
});

// 3. Wrap Route with HOC — enables parameterized transaction names
const SentryRoute = Sentry.withSentryRouting(Route);

// 4. Use SentryRoute everywhere instead of Route
//    ORDER MATTERS — most specific paths first (decreasing specificity)
function App() {
  return (
    <Router history={history}>
      <Switch>
        <SentryRoute path="/users/:userId/settings" component={UserSettingsPage} />
        <SentryRoute path="/users/:userId"          component={UserPage} />
        <SentryRoute path="/users"                  component={UsersPage} />
        <SentryRoute path="/"                       component={HomePage} />
      </Switch>
    </Router>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
Method 2 — Static Route Config (no HOC)
import { matchPath } from "react-router-dom";
import { createBrowserHistory } from "history";
import * as Sentry from "@sentry/react";

const history = createBrowserHistory();

// Define all routes; most specific first
const routes = [
  { path: "/users/:userId/settings" },
  { path: "/users/:userId" },
  { path: "/users" },
  { path: "/dashboard/analytics" },
  { path: "/dashboard" },
  { path: "/" },
];

Sentry.init({
  dsn: "...",
  integrations: [
    Sentry.reactRouterV5BrowserTracingIntegration({
      history,
      routes,
      matchPath,   // from react-router-dom
    }),
  ],
  tracesSampleRate: 1.0,
});

React Router v4: use Sentry.reactRouterV4BrowserTracingIntegration — the API is identical to v5.


TanStack Router

Requires: @tanstack/react-router ≥1.64.0

// src/main.tsx
import * as Sentry from "@sentry/react";
import { createRouter, RouterProvider } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";   // generated by TanStack Router

// 1. Create the router first
const router = createRouter({
  routeTree,
  defaultPreload: "intent",
});

// 2. Init Sentry, passing the router instance
Sentry.init({
  dsn: import.meta.env.VITE_SENTRY_DSN,
  integrations: [
    Sentry.tanstackRouterBrowserTracingIntegration(router),
  ],
  tracesSampleRate: 1.0,
  tracePropagationTargets: ["localhost", /^https:\/\/api\.myapp\.com/],
});

// 3. Render
ReactDOM.createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <Sentry.ErrorBoundary fallback={<p>An error has occurred</p>}>
      <RouterProvider router={router} />
    </Sentry.ErrorBoundary>
  </StrictMode>
);

Key difference vs React Router: tanstackRouterBrowserTracingIntegration takes the router instance directly — no hooks (useLocation, useNavigationType) or helpers (createRoutesFromChildren, matchRoutes) are needed. TanStack Router exposes its route definitions directly to the SDK.


How Route Names Are Parameterized

All router integrations extract parameterized route patterns instead of literal URLs:

Actual URL Transaction Name
/users/42 /users/:userId
/orders/abc-123/items /orders/:orderId/items
/posts/2024/my-first-post /posts/:year/:slug

This grouping is essential for meaningful performance data — without it, every user generates a unique transaction name and nothing can be aggregated.


Router Integration Quick-Reference

Are you using React Router?
├─ v7 (react-router package) ──────► reactRouterV7BrowserTracingIntegration
│    ├─ createBrowserRouter? ──────► wrapCreateBrowserRouterV7(createBrowserRouter)
│    ├─ createMemoryRouter? ───────► wrapCreateMemoryRouterV7(createMemoryRouter)
│    ├─ <Routes> component? ───────► withSentryReactRouterV7Routing(Routes)
│    └─ useRoutes hook? ────────────► wrapUseRoutesV7(useRoutes)
│
├─ v6 (react-router-dom) ──────────► reactRouterV6BrowserTracingIntegration
│    ├─ createBrowserRouter? ──────► wrapCreateBrowserRouterV6(createBrowserRouter)
│    ├─ createMemoryRouter? ───────► wrapCreateMemoryRouterV6(createMemoryRouter) [≥8.50.0]
│    ├─ <Routes> component? ───────► withSentryReactRouterV6Routing(Routes)
│    └─ useRoutes hook? ────────────► wrapUseRoutesV6(useRoutes)
│
├─ v4/v5 ───────────────────────────► reactRouterV5BrowserTracingIntegration({ history })
│    ├─ with static routes array ──► add { routes, matchPath }
│    └─ without static routes ─────► withSentryRouting(Route) HOC
│
└─ No router / unsupported router ──► browserTracingIntegration()
     └─ custom router ──────────────► { instrumentPageLoad: false, instrumentNavigation: false }
                                       + startBrowserTracingPageLoadSpan
                                       + startBrowserTracingNavigationSpan

Are you using TanStack Router?
└─ Any version ≥1.64.0 ─────────────► tanstackRouterBrowserTracingIntegration(router)

Custom Spans

The Three Span APIs

Sentry.startSpan() — Active, Auto-Ending (Recommended)

Wraps a block of work. The span is active (collects children) and automatically ends when the callback returns or resolves:

// Asynchronous
const data = await Sentry.startSpan(
  {
    name: "fetchUserProfile",
    op: "http.client",
    attributes: {
      "user.id": userId,
      "cache.hit": false,
    },
  },
  async () => {
    const res = await fetch(`/api/users/${userId}`);
    return res.json();
  }
);

// Synchronous
const result = Sentry.startSpan(
  { name: "computeRecommendations", op: "function" },
  () => expensiveComputation()
);

// Thrown errors are captured and the span is marked as error automatically
Sentry.startSpanManual() — Active, Manual End

Use when the span lifetime cannot be enclosed in a single callback — e.g., middleware that calls next():

function authMiddleware(req: Request, res: Response, next: NextFunction) {
  return Sentry.startSpanManual(
    { name: "auth.verify", op: "middleware" },
    (span) => {
      // span is active inside this callback only
      res.once("finish", () => {
        span.setStatus({ code: res.statusCode < 400 ? 1 : 2 });
        span.end();  // ← REQUIRED — will not end automatically
      });
      return next();
    }
  );
}
Sentry.startInactiveSpan() — Not Active, Manual End

For spans that cross event boundaries and should not automatically collect children as parent:

let checkoutSpan: Sentry.Span | undefined;

// On flow start
document.getElementById("checkout-btn")!.addEventListener("click", () => {
  checkoutSpan = Sentry.startInactiveSpan({
    name: "checkout-flow",
    op: "ui.flow",
  });
});

// On flow end (later, in a different event handler)
document.getElementById("confirm-btn")!.addEventListener("click", () => {
  checkoutSpan?.setAttribute("payment.method", "stripe");
  checkoutSpan?.setStatus({ code: 1 });
  checkoutSpan?.end();  // ← REQUIRED
});

Explicit parent-child wiring with inactive spans:

const parentSpan = Sentry.startInactiveSpan({ name: "checkout-flow" });

const childA = Sentry.startInactiveSpan({
  name: "validate-cart",
  op: "function",
  parentSpan,           // ← explicit parent reference
});
await validateCart();
childA.end();

const childB = Sentry.startInactiveSpan({
  name: "process-payment",
  op: "function",
  parentSpan,
});
await processPayment();
childB.end();

parentSpan.end();

Span Options Reference

interface StartSpanOptions {
  name: string;             // Required — label shown in the UI
  op?: string;              // Operation category (see table below)
  startTime?: number;       // Unix timestamp in seconds (can be float)
  attributes?: Record<string, string | number | boolean | string[] | number[] | boolean[]>;
  parentSpan?: Span;        // Override default parent (mainly for startInactiveSpan)
  onlyIfParent?: boolean;   // Drop this span if there is no currently active parent
  forceTransaction?: boolean; // Force span to appear as a root transaction in the UI
}

Common op values:

op When to use
http.client Outgoing HTTP requests
db.query Database queries
ui.render React component render work
ui.load Async data loading for a page/view
ui.click User click event handling
ui.flow Multi-step UI flow (checkout, wizard)
function General JS function calls
task Background/scheduled work
cache.get / cache.set Cache reads/writes
middleware Express/Koa/Fastify middleware

Enriching Spans

const span = Sentry.getActiveSpan();

if (span) {
  // Single attribute
  span.setAttribute("db.table", "users");
  span.setAttribute("db.rows_affected", 5);

  // Multiple attributes at once
  span.setAttributes({
    "http.method": "POST",
    "http.status_code": 201,
    "user.tier": "premium",
  });

  // Status codes: 0=unset, 1=ok, 2=error
  span.setStatus({ code: 1 });
  span.setStatus({ code: 2, message: "Upstream timeout" });

  // HTTP-specific shorthand
  span.setHttpStatus(404);  // sets code=2, message="Not Found"
  span.setHttpStatus(200);  // sets code=1

  // Rename at runtime
  span.updateName("GET /users/:id");

  // End with explicit timestamp (seconds since epoch)
  span.end(Date.now() / 1000);
}

Nesting Spans

Children nest automatically under the currently active span:

await Sentry.startSpan({ name: "loadDashboard", op: "ui.load" }, async () => {
  // These are children of "loadDashboard"
  const [user, posts] = await Promise.all([
    Sentry.startSpan({ name: "fetchUser", op: "http.client" }, () =>
      fetch("/api/user").then(r => r.json())
    ),
    Sentry.startSpan({ name: "fetchPosts", op: "http.client" }, () =>
      fetch("/api/posts").then(r => r.json())
    ),
  ]);

  // Sequential child — still nested under "loadDashboard"
  await Sentry.startSpan({ name: "renderDashboard", op: "ui.render" }, async () => {
    await renderContent(user, posts);
  });
});

forceTransaction — Standalone Root Span

Forces a span to appear as its own root transaction in the Sentry UI, independent of any active parent. Useful for background workers, Web Workers, or queue processors:

Sentry.startSpan(
  { name: "processEmailQueue", op: "task", forceTransaction: true },
  async () => {
    const batch = await queue.take(50);
    await processBatch(batch);
  }
);

Browser Flat Span Hierarchy

In browsers, all child spans are attached flat to the root span (not nested under intermediate parents). This prevents incorrect parent-child attribution in parallel async flows.

To opt into true nesting (use with care):

Sentry.init({
  // ...
  parentSpanIsAlwaysRootSpan: false,
});

Distributed Tracing

Distributed tracing connects a browser page load to all backend API calls it triggers, creating a single end-to-end waterfall.

The Two Headers

Header Format Purpose
sentry-trace {traceId}-{spanId}-{sampled} Carries trace context
baggage W3C Baggage format with sentry-* entries Carries sampling decision + metadata

Both headers are automatically injected into fetch() and XMLHttpRequest for URLs matching tracePropagationTargets.

tracePropagationTargets

Sentry.init({
  tracePropagationTargets: [
    // String = substring match against full URL
    "localhost",
    "api.myapp.com",

    // RegExp = tested against full URL
    /^https:\/\/api\.myapp\.com\//,
    /^\/api\//,  // same-origin relative paths

    // Multiple backends
    "https://auth.myapp.com",
    "https://payments.myapp.com",
  ],
});

Defaults: Same-origin requests get headers automatically. Cross-origin requests need explicit entries.

Disable completely:

tracePropagationTargets: []  // no distributed tracing headers on any requests

CORS Requirements

Your backend APIs must allowlist these headers:

Access-Control-Allow-Headers: sentry-trace, baggage

Express example:

app.use((_req, res, next) => {
  res.setHeader(
    "Access-Control-Allow-Headers",
    "Content-Type, Authorization, sentry-trace, baggage"
  );
  next();
});

Without this, preflight requests fail and browsers suppress the headers.

SSR / Meta Tag Approach

When your HTML is server-rendered, emit Sentry trace context into <meta> tags. browserTracingIntegration reads them on init and attaches the pageload span to the server's trace — the full request becomes one continuous trace.

Server (Node.js/Express):

import * as Sentry from "@sentry/node";

app.get("/", (_req, res) => {
  const traceData = Sentry.getTraceData();
  // { "sentry-trace": "...", baggage: "..." }
  res.render("index", {
    sentryTrace: traceData["sentry-trace"],
    sentryBaggage: traceData["baggage"],
  });
});

HTML template (EJS/Handlebars/Jinja/etc.):

<head>
  <meta name="sentry-trace" content="<%= sentryTrace %>" />
  <meta name="baggage"      content="<%= sentryBaggage %>" />
</head>

The browser SDK reads these tags automatically — no extra client config needed.

Manual Propagation (WebSockets, Custom Channels)

For protocols that don't support HTTP headers:

// Browser (sender)
const traceData = Sentry.getTraceData();

socket.send(JSON.stringify({
  type: "rpc.updateProfile",
  payload: { name: "Alice" },
  _sentry: {
    trace: traceData["sentry-trace"],
    baggage: traceData["baggage"],
  },
}));
// Node.js server (receiver)
import * as Sentry from "@sentry/node";
import { propagation, context } from "@opentelemetry/api";

socket.on("message", (raw) => {
  const msg = JSON.parse(raw);
  const ctx = propagation.extract(context.active(), {
    "sentry-trace": msg._sentry.trace,
    "baggage": msg._sentry.baggage,
  });
  context.with(ctx, () => {
    Sentry.startSpan({ name: "ws.updateProfile" }, () => handleMessage(msg));
  });
});

W3C traceparent Compatibility (SDK ≥10.10.0)

Add the W3C traceparent header alongside sentry-trace for OpenTelemetry-native backends:

Sentry.init({
  propagateTraceparent: true,
});

Sampling

tracesSampleRate — Uniform Rate

Sentry.init({
  tracesSampleRate: 1.0,   // 100% — dev / staging / low-traffic
  // tracesSampleRate: 0.2,  // 20% — light production
  // tracesSampleRate: 0.05, // 5%  — high-traffic production
  // tracesSampleRate: 0.01, // 1%  — very high-traffic production
});

tracesSampler — Dynamic Per-Transaction

tracesSampler replaces tracesSampleRate (when both are set, tracesSampler wins):

Sentry.init({
  tracesSampler: ({ name, attributes, inheritOrSampleWith }) => {
    // Drop health checks and internal routes
    if (["/health", "/ping", "/readyz"].some(p => name.includes(p))) return 0;

    // Always capture critical flows
    if (name.startsWith("/checkout") || name.startsWith("/payment")) return 1.0;

    // Sample admin routes at 50%
    if (name.startsWith("/admin")) return 0.5;

    // High-volume search at 5%
    if (name.includes("/search")) return 0.05;

    // For everything else: honor parent's decision, fall back to 10%
    return inheritOrSampleWith(0.1);
  },
});

Full samplingContext Object

interface SamplingContext {
  name: string;                     // Span/transaction name (e.g. "GET /users/:id")
  attributes?: SpanAttributes;      // Initial span attributes: op, url, http.method, etc.
  parentSampled?: boolean;          // Was the parent trace sampled? undefined = no parent
  parentSampleRate?: number;        // What rate was used upstream?
  inheritOrSampleWith: (fallbackRate: number) => number;
}

inheritOrSampleWith — Why It Matters

Use inheritOrSampleWith(fallback) instead of checking parentSampled directly. It enables:

  • Deterministic sampling: the same rate decision is applied throughout the trace chain
  • Accurate metric extrapolation: Sentry's performance metrics scale correctly only when consistent sample rates flow through all services
  • Correct Sampled flag: ensures the sentry-sampled value in downstream baggage matches the actual decision

Returning Boolean vs Number

tracesSampler: ({ name }) => {
  if (name === "/critical")  return true;   // equivalent to 1.0
  if (name === "/noisy")     return false;  // equivalent to 0
  return 0.2;
}

Sampling Guidelines by Traffic Level

Daily transactions Recommended tracesSampleRate
< 10K 1.0 — capture everything
10K–100K 0.2 — 20%
100K–1M 0.05 – 0.1
> 1M 0.01 – 0.02 with tracesSampler for priority routes at higher rates

Span Filtering

beforeSendTransaction — Modify or Drop Whole Transactions

Sentry.init({
  beforeSendTransaction(event) {
    // Drop internal/dev routes
    if (event.transaction?.startsWith("/__internal")) return null;

    // Scrub PII from transaction names
    if (event.transaction) {
      event.transaction = event.transaction
        .replace(/\/users\/[^/]+/, "/users/<redacted>");
    }

    // Add custom tags to every transaction
    event.tags = { ...event.tags, "app.build": BUILD_ID };

    return event;
  },
});

ignoreTransactions — Declarative Transaction Filtering

Sentry.init({
  ignoreTransactions: [
    "/health",            // string = substring match
    /^\/api\/internal/,   // regex = full URL test
    "/__webpack_hmr",
    /\.(png|jpg|svg|ico|woff2)$/,  // static assets
  ],
});

beforeSendSpan — Modify Individual Spans

beforeSendSpan cannot drop spans — it can only modify them. To suppress spans, use ignoreSpans (SDK ≥10.2.0).

Sentry.init({
  beforeSendSpan(span) {
    // Redact token from span descriptions
    if (span.op === "http.client" && span.description?.includes("/token")) {
      span.description = span.description.replace(/token=[^&]+/, "token=REDACTED");
    }

    // Enrich all spans with deployment info
    span.data = {
      ...span.data,
      "deployment.region": import.meta.env.VITE_AWS_REGION ?? "unknown",
    };

    return span;  // must return span — never return null
  },
});

ignoreSpans — Declarative Span Filtering (SDK ≥10.2.0)

Sentry.init({
  ignoreSpans: [
    // String — matches against span name/description
    "font-load",

    // Regex against span name
    /^performance\.mark\./,

    // Object — filter by op only
    { op: "resource.script" },
    { op: "resource.img" },
    { op: "resource.css" },

    // Object — filter by name and op together
    { name: "beacon", op: "http.client" },

    // Object — name regex
    { name: /^(hotjar|analytics|gtag)/ },
  ],
});

Warning: If the root span (the transaction itself) matches an ignoreSpans rule, the entire local trace is dropped.


Custom Routing (Manual Spans)

For unsupported or custom routers, disable auto page spans and drive them yourself:

import * as Sentry from "@sentry/react";
import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from "@sentry/react";

const client = Sentry.init({
  dsn: "...",
  integrations: [
    Sentry.browserTracingIntegration({
      instrumentPageLoad: false,   // handled manually
      instrumentNavigation: false, // handled manually
    }),
  ],
  tracesSampleRate: 1.0,
})!;

// Initial page load — name with URL until route is matched
let pageLoadSpan = Sentry.startBrowserTracingPageLoadSpan(client, {
  name: window.location.pathname,
  attributes: {
    [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: "url",  // start with "url" source
  },
});

// Once the router resolves the matched route
myCustomRouter.on("routeResolved", (route) => {
  if (pageLoadSpan) {
    // Upgrade the pageload span's name to the parameterized pattern
    pageLoadSpan.updateName(route.pattern);          // e.g. "/users/:id"
    pageLoadSpan.setAttribute(
      SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, "route"      // upgrade to "route" source
    );
    pageLoadSpan = undefined;
  } else {
    // Subsequent navigations
    Sentry.startBrowserTracingNavigationSpan(client, {
      op: "navigation",
      name: route.pattern,
      attributes: {
        [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: "route",
      },
    });
  }
});

Both functions create idle spans — they close automatically after idleTimeoutms of no new child activity, matching the behavior of automatic pageload/navigation spans.


Full Import Reference

import * as Sentry from "@sentry/react";

// ── Integrations ──────────────────────────────────────────────────────────
Sentry.browserTracingIntegration(options)
Sentry.reactRouterV7BrowserTracingIntegration(options)
Sentry.reactRouterV6BrowserTracingIntegration(options)
Sentry.reactRouterV5BrowserTracingIntegration(options)
Sentry.reactRouterV4BrowserTracingIntegration(options)
Sentry.tanstackRouterBrowserTracingIntegration(router)

// ── Router Wrappers — v7 ─────────────────────────────────────────────────
Sentry.wrapCreateBrowserRouterV7(createBrowserRouter)
Sentry.wrapCreateMemoryRouterV7(createMemoryRouter)
Sentry.withSentryReactRouterV7Routing(Routes)
Sentry.wrapUseRoutesV7(useRoutes)

// ── Router Wrappers — v6 ─────────────────────────────────────────────────
Sentry.wrapCreateBrowserRouterV6(createBrowserRouter)
Sentry.wrapCreateMemoryRouterV6(createMemoryRouter)    // SDK ≥8.50.0
Sentry.withSentryReactRouterV6Routing(Routes)
Sentry.wrapUseRoutesV6(useRoutes)

// ── Router Wrappers — v5/v4 ──────────────────────────────────────────────
Sentry.withSentryRouting(Route)

// ── Spans ────────────────────────────────────────────────────────────────
Sentry.startSpan(options, callback)
Sentry.startSpanManual(options, callback)
Sentry.startInactiveSpan(options)
Sentry.getActiveSpan()

// ── Custom Browser Tracing ───────────────────────────────────────────────
Sentry.startBrowserTracingPageLoadSpan(client, options)
Sentry.startBrowserTracingNavigationSpan(client, options)

// ── Distributed Tracing ──────────────────────────────────────────────────
Sentry.getTraceData()
// Returns: { "sentry-trace": string, baggage: string }

// ── Constants ────────────────────────────────────────────────────────────
Sentry.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE  // "sentry.source"
Sentry.SEMANTIC_ATTRIBUTE_SENTRY_OP      // "sentry.op"

Troubleshooting

Issue Solution
No transactions in Performance dashboard Verify tracesSampleRate > 0; confirm browserTracingIntegration() (or router variant) is in integrations array
Transaction names show raw URLs (/users/42) instead of patterns Add router integration matching your router version; ensure it's replacing, not supplementing, browserTracingIntegration()
Transaction named <unknown> Router integration is missing or misconfigured; check useEffect, useLocation, useNavigationType are all passed correctly
Distributed trace not linking frontend → backend Add backend URL to tracePropagationTargets; verify Access-Control-Allow-Headers includes sentry-trace, baggage
SSR page load not linked to server trace Inject <meta name="sentry-trace"> and <meta name="baggage"> tags from Sentry.getTraceData() in server-rendered HTML
API requests missing sentry-trace header Check CORS preflight — backend must allow sentry-trace and baggage headers
INP spans not appearing In SDK 7.x, enable explicitly: browserTracingIntegration({ enableInp: true })
Web Vitals missing Confirm browserTracingIntegration() is in client init; check browser support (INP requires Chromium 96+)
Spans missing after async gap Browser uses flat hierarchy; use startInactiveSpan with explicit parentSpan to enforce parent-child across async boundaries
High transaction volume / cost Use tracesSampler to return 0 for health checks and asset routes; lower default rate with inheritOrSampleWith(0.05)
beforeSendSpan returning null breaks the SDK beforeSendSpan must always return the span — use ignoreSpans to drop spans declaratively
Lazy routes not tracked Upgrade to SDK ≥10.39.0; add enableAsyncRouteHandlers: true and lazyRouteManifest with all route paths
TanStack Router transactions missing Ensure router is created before Sentry.init() is called and the router instance is passed to the integration

Source: SKILL.md on GitHub

1 alert17d5 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    This skill facilitates the integration of the Sentry React SDK into web applications. It provides systematic guidance for environment detection, package installation, and SDK configuration for features like error monitoring, tracing, and session replay. All recommended tools and practices align with standard vendor documentation and security best practices for the 'getsentry' author.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: LOW · No issues

  • Runlayer7mo

    7/7 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 98a20d1. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last week.

Dormantupdated 7 months ago

README badge

README badge for getsentry/sentry-agent-skills/sentry-react-sdk