Set Up Tracing

Monitor performance across all Next.js runtimes: client, server, and edge.

Sentry Agent Skills

Install Sentry's agent skills to teach your AI coding assistant how to set up tracing in your Next.js application.

Copied
npx @sentry/dotagents add getsentry/sentry-agent-skills --name sentry-setup-tracing

See the full list of available skills and installation docs for more details.

Tracing captures the timing and flow of requests through your Next.js application. Learn more about tracing and how Sentry uses traces to track performance across services.

Sentry automatically instruments most operations, but Server Actions require manual setup.

Before enabling tracing, ensure you have:

  • Installed the Sentry SDK in your Next.js application
  • Initialized Sentry in all three config files (instrumentation-client.ts, sentry.server.config.ts, sentry.edge.config.ts)

Add tracesSampleRate to your Sentry configuration in all three runtime files.

Start with 100% in development, and 10-20% in production. Adjust based on traffic volume and budget.

instrumentation-client.ts
Copied
import * as Sentry from "@sentry/nextjs";

Sentry.init({
  dsn: "___PUBLIC_DSN___",
  tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1,
});

The SDK instruments different operations depending on where your code runs:

RuntimeAuto-Instrumented
ClientPage loads, navigations, fetch requests, Web Vitals
ServerAPI routes, Server Components, getServerSideProps
EdgeMiddleware, edge API routes
Copied
// All of these create spans automatically

// Client: navigation creates a span
<Link href="/dashboard">Dashboard</Link>;

// Server: API route creates a span
export async function GET() {
  return Response.json({ data });
}

// Server Component: creates a span
export default async function Page() {
  const data = await fetchData();
  return <div>{data}</div>;
}

For configuration options like timeouts, INP settings, and filtering spans, see Automatic Instrumentation.

Server Actions are not automatically instrumented. Without wrapping, they appear as anonymous server operations with no timing or context.

Use withServerActionInstrumentation to:

  • Create named spans for each action
  • Capture timing and errors
  • Connect client and server traces via headers
  • Attach form data to Sentry events via formData
app/actions.ts
Copied
"use server";

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

export async function createOrder(formData: FormData) {
  return Sentry.withServerActionInstrumentation(
    "createOrder",
    async () => {
      // Your action logic
      const order = await db.orders.create({
        data: { items: formData.get("items") },
      });
      return { success: true, orderId: order.id };
    },
  );
}

Pass headers to connect client-side traces with server-side spans for full distributed tracing across the browser-to-server boundary. Pass formData to attach submitted form data to Sentry events for easier debugging.

app/actions.ts
Copied
"use server";

import * as Sentry from "@sentry/nextjs";
import { headers } from "next/headers";

export async function submitForm(formData: FormData) {
  return Sentry.withServerActionInstrumentation(
    "submitForm",
    {
      headers: await headers(), // Connect client and server traces
      formData, // Attach form data to events
      recordResponse: true, // Include response data
    },
    async () => {
      // Action logic with full trace context
    },
  );
}

The SDK automatically captures Web Vitals on every page load. These metrics measure real user experience:

MetricWhat It MeasuresThreshold (Good)
LCPLargest Contentful Paint — loading performance≤ 2.5s
INPInteraction to Next Paint — responsiveness≤ 200ms
CLSCumulative Layout Shift — visual stability≤ 0.1
FCPFirst Contentful Paint — initial render≤ 1s
TTFBTime to First Byte — server response≤ 100ms

Web Vitals appear as measurements on page load transactions and feed into your Performance Score. See Web Vitals Concepts for detailed explanations of each metric.

Add custom spans when you want to measure:

  • Business operations (checkout flow, multi-step wizards)
  • External API calls you want to track separately
  • Database operations not auto-captured
  • Expensive computations

For the full span API including startSpanManual and startInactiveSpan, see Custom Instrumentation.

app/api/checkout/route.ts
Copied
import * as Sentry from "@sentry/nextjs";

export async function POST(request: Request) {
  return Sentry.startSpan(
    { name: "checkout.process", op: "checkout" },
    async () => {
      // Nested spans for sub-operations
      const cart = await Sentry.startSpan(
        { name: "checkout.validate_cart", op: "validation" },
        () => validateCart(request),
      );

      const payment = await Sentry.startSpan(
        { name: "checkout.process_payment", op: "payment" },
        () => processPayment(cart),
      );

      return Response.json({ orderId: payment.orderId });
    },
  );
}

Attach data to spans for filtering and debugging in Sentry.

Copied
Sentry.startSpan(
  {
    name: "order.process",
    op: "order",
    attributes: {
      "order.id": orderId,
      "order.value": cart.total,
      "order.item_count": cart.items.length,
    },
  },
  async () => {
    // Operation logic
  },
);

The SDK automatically propagates trace context for:

  • fetch() requests to your own domain
  • Server Actions (when using withServerActionInstrumentation with headers)

For external APIs, configure tracePropagationTargets:

instrumentation-client.ts
Copied
Sentry.init({
  dsn: "___PUBLIC_DSN___",
  tracesSampleRate: 0.1,

  // Propagate traces to these external services
  tracePropagationTargets: [
    "localhost",
    /^https:\/\/api\.yourcompany\.com/,
    /^https:\/\/payments\.stripe\.com/,
  ],
});

For CORS configuration and manual trace propagation (WebSockets, etc.), see Distributed Tracing.

ScenarioAuto-Instrumented?Action
Page loads and navigationsYesNone
API routes (route.ts)YesNone
Server ComponentsYesNone
getServerSidePropsYesNone
Server ActionsNoUse withServerActionInstrumentation
External API callsPartialConfigure tracePropagationTargets
Custom business logicNoUse startSpan

If you're sending too many traces, lower your sample rate or use tracesSampler for dynamic sampling based on route.

See Configure Sampling for more options.

Copied
Sentry.init({
  tracesSampler: ({ name }) => {
    // Sample 5% of health checks
    if (name.includes("health")) return 0.05;
    // Sample 50% of API routes
    if (name.includes("/api/")) return 0.5;
    // Default 10%
    return 0.1;
  },
});
Was this helpful?
Help improve this content
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").