---
title: "Trace Profiling"
description: "Next.js incorporates robust tracing and performance profiling subsystems designed to capture, record, and export telemetry data across both compilation pipelines and runtime request handlers. By le..."
last_updated: "2026-10-12T06:38:56.875594+00:00"
canonical_url: "https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/cli-telemetry/trace-profiling"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [packages/next/src/server/lib/trace/tracer.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/tracer.ts)
- [packages/next/src/server/lib/trace/local-span-recorder.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/local-span-recorder.ts)
- [packages/next/src/build/index.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/index.ts)
- [packages/next/src/server/lib/trace/constants.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/constants.ts)
- [packages/next/src/build/webpack/plugins/profiling-plugin.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/profiling-plugin.ts)
- [packages/next/src/build/collect-build-traces.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/collect-build-traces.ts)
- [packages/next/src/trace/trace.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/trace/trace.ts)
- [packages/next/src/server/route-modules/pages/pages-handler.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/pages/pages-handler.ts)
- [packages/next/src/cli/internal/turbo-trace-server.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/cli/internal/turbo-trace-server.ts)
- [packages/next/src/build/turbopack-build/index.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/turbopack-build/index.ts)
- [packages/next/src/server/lib/trace/request-insights.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/request-insights.ts)
- [packages/next/src/server/lib/router-utils/instrumentation-globals.external.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/router-utils/instrumentation-globals.external.ts)
- [packages/next/src/server/lib/router-utils/instrumentation-node-extensions.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/router-utils/instrumentation-node-extensions.ts)
- [packages/next/src/build/webpack/plugins/rspack-profiling-plugin.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/rspack-profiling-plugin.ts)
- [packages/next/src/server/lib/trace/span-store.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/span-store.ts)
- [packages/next/src/server/dev/next-dev-server.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/dev/next-dev-server.ts)
- [packages/next/src/build/analyze/index.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/analyze/index.ts)
- [packages/next/src/trace/trace-uploader.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/trace/trace-uploader.ts)
- [packages/next/src/bin/next.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/bin/next.ts)
- [packages/next/src/trace/index.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/trace/index.ts)
- [packages/next/src/build/utils.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/utils.ts)
- [packages/next/src/next-devtools/dev-overlay/components/request-insights/trace-viewer.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/next-devtools/dev-overlay/components/request-insights/trace-viewer.ts)
- [packages/next/src/trace/report/to-telemetry.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/trace/report/to-telemetry.ts)
- [packages/next/src/build/webpack/utils.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/utils.ts)
- [packages/next/src/build/webpack/plugins/next-trace-entrypoints-plugin.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/next-trace-entrypoints-plugin.ts)
- [packages/next/src/build/swc/generated-native.d.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/swc/generated-native.d.ts)
- [packages/next/src/server/lib/trace/request-insights-identity.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/request-insights-identity.ts)
- [packages/next/src/lib/memory/trace.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/lib/memory/trace.ts)
</details>

## Overview

Next.js incorporates robust tracing and performance profiling subsystems designed to capture, record, and export telemetry data across both compilation pipelines and runtime request handlers. By leveraging standard OpenTelemetry instrumentation alongside custom local span recording mechanisms, the tracing engine provides granular visibility into execution paths, network request waterfalls, and resource utilization.
Sources: [packages/next/src/server/lib/trace/tracer.ts:223-278](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/tracer.ts#L223-L278), [packages/next/src/server/lib/trace/local-span-recorder.ts:58-81](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/local-span-recorder.ts#L58-L81)

The system addresses the challenge of diagnosing performance bottlenecks in complex asynchronous environments by combining asynchronous local storage propagation, bundler compilation hooks for Webpack and Rspack, output file dependency tracing via Vercel's NFT, and periodic V8 memory heap tracking.
Sources: [packages/next/src/build/webpack/plugins/profiling-plugin.ts:28-47](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/profiling-plugin.ts#L28-L47), [packages/next/src/build/collect-build-traces.ts:94-126](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/collect-build-traces.ts#L94-L126), [packages/next/src/lib/memory/trace.ts:27-54](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/lib/memory/trace.ts#L27-L54)

## Tracer Architecture and Core Span Lifecycle

### Public Tracer Interface and Active Scope Management

Next.js exports a unified tracing surface combining OpenTelemetry wrappers with custom span management primitives. The `NextTracerImpl` class implements active scope resolution and context propagation using `@opentelemetry/api`. Active scope tracking through `getActiveScopeSpan()` inspects local span recorders when running in development mode before falling back to `trace.getSpan(context.active())`.

> [!NOTE]
> `NextTracerImpl` attempts to require the user's installed version of `@opentelemetry/api` first to maintain compatibility with external OpenTelemetry tracing setups, falling back to the version bundled with Next.js only if the require call fails.

Context propagation is managed via `withPropagatedContext` and `runWithDetachedContext`. When executing synchronous or asynchronous blocks where upstream trace headers must be honored, `withPropagatedContext` extracts remote context carriers via `propagation.extract` using a provided or default `TextMapGetter`. If `force` is enabled, any extracted remote span context overrides the active context while merging baggage values. Conversely, `runWithDetachedContext` executes functions under `ROOT_CONTEXT`, ensuring that in-process Node.js middleware spawns sibling spans rather than nested child spans, matching the detached sandbox behavior of edge middleware.

Sources: [packages/next/src/server/lib/trace/tracer.ts:42-50](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/tracer.ts#L42-L50), [packages/next/src/server/lib/trace/tracer.ts:252-330](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/tracer.ts#L252-L330)

### OpenTelemetry Span Lifecycle Hooks

Span creation and duration recording are managed by the `Span` class in `packages/next/src/trace/trace.ts` alongside predefined span type enumerations. When a span starts, it captures a high-resolution timestamp via `process.hrtime.bigint()` and a wall-clock fallback via `Date.now()` to correlate monotonic durations with absolute epoch times.

```mermaid
sequenceDiagram
    participant Caller
    participant Span as Span Instance
    participant Reporter as reporter

    Caller->>Span: constructor / trace() / traceAsyncFn()
    Note over Span: Capture hrtime & wall time (Date.now)
    Caller->>Span: Execution of wrapped function
    alt Function succeeds or throws
        Span->>Span: stop() -> compute duration (microseconds)
        alt duration > RECORD_SPAN_THRESHOLD_MS
            Span->>Reporter: report(traceEvent)
        end
    end
```
Sources: [packages/next/src/trace/trace.ts:30-164](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/trace/trace.ts#L30-L164)

The `Span` lifecycle supports manual and automatic tracking. Durations are calculated as nanosecond differences scaled down to microfluidic integer microseconds to fit safely within float64 bounds (`Number.MAX_SAFE_INTEGER`). If the calculated duration exceeds `RECORD_SPAN_THRESHOLD_MS * 1000`, the resulting `TraceEvent` is dispatched to the global `reporter`.

| Span Enumeration | Description / Key Members | Sources |
| --- | --- | --- |
| `BaseServerSpan` | Request handling, pipeline execution, server-side rendering (`handleRequest`, `render`, `renderToHTML`) | [packages/next/src/server/lib/trace/constants.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/constants.ts#L10-L23) |
| `NextNodeServerSpan` | Node server internals, component tree creation, routing, [static generation](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/build-system/static-generation) (`createComponentTree`, `findPageComponents`, `runApi`) | [packages/next/src/server/lib/trace/constants.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/constants.ts#L44-L77) |
| `AppRenderSpan` | [App Router](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-runtime/app-router) rendering phases (`renderToString`, `renderToReadableStream`, `fetch`, `waitShellReady`) | [packages/next/src/server/lib/trace/constants.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/constants.ts#L91-L101) |
| `RenderSpan` | Pages Router data fetching and rendering (`getServerSideProps`, `getStaticProps`, `renderDocument`) | [packages/next/src/server/lib/trace/constants.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/constants.ts#L83-L89) |
| `LoadComponents` | Component and route module loading (`loadComponents`, `loadRouteModule`) | [packages/next/src/server/lib/trace/constants.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/constants.ts#L25-L29) |
| `MiddlewareSpan` | Middleware execution tracing (`execute`) | [packages/next/src/server/lib/trace/constants.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/constants.ts#L132-L134) |

> [!CAUTION]
> Calling `.stop()` multiple times on the same `Span` instance is guarded by a status check against `SpanStatus.Stopped`, returning early without reporting duplicate trace events. However, callers must ensure asynchronous functions wrapped with `traceAsyncFn()` properly await completion so that `finally` blocks call `stop()` at the correct boundary.

Sources: [packages/next/src/server/lib/trace/constants.ts:10-134](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/constants.ts#L10-L134), [packages/next/src/trace/trace.ts:75-103](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/trace/trace.ts#L75-L103), [packages/next/src/trace/trace.ts:154-164](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/trace/trace.ts#L154-L164)

## Local Span Recording and Storage

### In-Memory Collection and Async Storage Propagation

Local span recording captures telemetry directly within the [development server](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/development/development-server) environment without exporting to external OpenTelemetry collectors. This subsystem relies on `AsyncLocalStorage` instances managed across two storage boundaries: `localSpanAsyncStorage` via `packages/next/src/server/lib/trace/local-span-recorder.ts` and `request-insights-identity-storage` via `packages/next/src/server/lib/trace/request-insights-identity.ts`. 

```mermaid
sequenceDiagram
    participant Caller
    participant Storage as AsyncLocalStorage
    participant Span as LocalRecordingSpan
    participant Store as SpanStore

    Caller->>Storage: withLocalSpan(span, fn)
    Caller->>Span: traceLocalSpan(options, fn)
    Note over Span: Resolve parent span & trace context
    Span->>Storage: run(span, async fn)
    alt Execution completes or throws
        Span->>Span: recordException / setStatus
        Span->>Store: recordSpan(spanRecord)
    end
```
Sources: [packages/next/src/server/lib/trace/local-span-recorder.ts:83-128](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/local-span-recorder.ts#L83-L128), [packages/next/src/server/lib/trace/request-insights-identity.ts:15-30](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/request-insights-identity.ts#L15-L30)

The function `createLocalSpan` instantiates `LocalRecordingSpan` with hex-encoded trace IDs and span IDs generated incrementally via `getLocalTraceId()` (32 hex characters) and `getLocalSpanId()` (16 hex characters). When `traceLocalSpan` wraps asynchronous operations, it automatically extracts the active local span's context, propagates parent identifiers, and isolates the span from the global OpenTelemetry pipeline by setting `isolateOpenTelemetry: true`.

> [!NOTE]
> [Request insights](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/development/request-insights) identity storage covers the portion of `BaseServer` request handling executing before [app rendering](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/app-rendering/app-rendering) establishes work storage. Once `workStore` is available, it functions as the primary identity source for locally recorded spans.

Sources: [packages/next/src/server/lib/trace/local-span-recorder.ts:48-128](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/local-span-recorder.ts#L48-L128), [packages/next/src/server/lib/trace/request-insights-identity.ts:12-17](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/request-insights-identity.ts#L12-L17)

### DevTools Integration and Recorder Registration

Span persistence and developer tooling integration are controlled by environment checks and registration functions defined in `packages/next/src/server/lib/trace/span-store.ts`. Local span recording is active only when `process.env.__NEXT_DEV_SERVER` is enabled alongside active test recorders or request insights flags.

| Function / Constant | Scope / Return Type | Description | Sources |
| --- | --- | --- | --- |
| `isLocalSpanRecordingEnabled()` | `boolean` | Checks if `__NEXT_DEV_SERVER` is present and either test recorders or request insights are enabled. | [packages/next/src/server/lib/trace/span-store.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/span-store.ts#L70-L76) |
| `isRequestInsightsEnabled()` | `boolean` | Validates if `process.env.__NEXT_REQUEST_INSIGHTS` matches enabled values (`1`, `true`, or boolean `true`). | [packages/next/src/server/lib/trace/span-store.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/span-store.ts#L78-L89) |
| `recordSpan(record)` | `void` | Attaches a timestamp to the span record and dispatches it to test recorders or request insight collectors. | [packages/next/src/server/lib/trace/span-store.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/span-store.ts#L45-L62) |
| `registerLocalSpanRecorder()` | `void` | Attaches a `LocalSpanRecorder` dictionary to `globalThis` under the symbol `@next/local-span-recorder`. | [packages/next/src/server/lib/trace/local-span-recorder.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/local-span-recorder.ts#L141-L157) |

> [!CAUTION]
> `recordSpan` drops incoming records immediately without processing if `isLocalSpanRecordingEnabled()` evaluates to false, ensuring zero overhead during production builds where development server flags are absent.

Sources: [packages/next/src/server/lib/trace/span-store.ts:45-76](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/trace/span-store.ts#L45-L76)

## Instrumentation Initialization and Node Augmentations

### Instrumentation Hook Loading and Registration

Server startup initialization requires locating and loading the user-defined instrumentation hook file (`instrumentation.js` or `instrumentation.ts` compiled into the distribution directory). The module loader function `getInstrumentationModule(projectDir, distDir)` caches the resolved instrumentation object to prevent duplicate disk reads, using `performance.timeOrigin + performance.performance.now()` via `getInstrumentationTimestamp()` to measure module load timings.

```typescript
export async function getInstrumentationModule(
  projectDir: string,
  distDir: string
): Promise<InstrumentationModule | undefined> {
  if (cachedInstrumentationModule) {
    return cachedInstrumentationModule
  }

  const startTime = getInstrumentationTimestamp()
  let error: unknown
  try {
    cachedInstrumentationModule = interopDefault(
      await require(
        path.join(
          projectDir,
          distDir,
          'server',
          `${INSTRUMENTATION_HOOK_FILENAME}.js`
        )
      )
    )
    return cachedInstrumentationModule
  } catch (err: unknown) {
    if (
      isError(err) &&
      err.code !== 'ENOENT' &&
      err.code !== 'MODULE_NOT_FOUND' &&
      err.code !== 'ERR_MODULE_NOT_FOUND'
    ) {
      error = err
      throw err
    }
  } finally {
    instrumentationModuleLoadTiming ??= {
      startTime,
      endTime: getInstrumentationTimestamp(),
      error,
    }
  }
}
```
Sources: [packages/next/src/server/lib/router-utils/instrumentation-globals.external.ts:62-101](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/router-utils/instrumentation-globals.external.ts#L62-L101)

When `registerInstrumentation(projectDir, distDir)` executes, it checks `process.env.NEXT_PHASE === 'phase-production-build'` to bypass registration during production compilation. If the instrumentation module exports a `register()` asynchronous function, it executes the lifecycle hook, triggers `extendInstrumentationAfterRegistration()`, and emits backdated spans for both module loading and registration phases.

```typescript
async function registerInstrumentation(projectDir: string, distDir: string) {
  // Ensure registerInstrumentation is not called in production build
  if (process.env.NEXT_PHASE === 'phase-production-build') {
    return
  }
  if (!instrumentationModulePromise) {
    instrumentationModulePromise = getInstrumentationModule(projectDir, distDir)
  }
  let instrumentation: InstrumentationModule | undefined
  try {
    instrumentation = await instrumentationModulePromise
  } catch (err) {
    // A load error is only exportable when a provider was already installed.
    traceInstrumentationModuleLoad()
    throw err
  }
  if (instrumentation?.register) {
    const startTime = getInstrumentationTimestamp()
    let error: unknown
    try {
      await instrumentation.register()
      extendInstrumentationAfterRegistration()
    } catch (err: any) {
      error = err
      err.message = `An error occurred while loading instrumentation hook: ${err.message}`
      throw err
    } finally {
      const endTime = getInstrumentationTimestamp()

      // register() commonly installs the provider, so emit both lifecycle
      // spans afterward with their original timestamps.
      traceInstrumentationModuleLoad()
      traceBackdatedInstrumentationSpan(
        InstrumentationSpan.register,
        'register instrumentation',
        { startTime, endTime, error }
      )
    }
  } else {
    traceInstrumentationModuleLoad()
  }
}
```
Sources: [packages/next/src/server/lib/router-utils/instrumentation-globals.external.ts:105-146](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/router-utils/instrumentation-globals.external.ts#L105-L146)

> [!WARNING]
> `getInstrumentationModule` ignores missing hook files by catching `ENOENT`, `MODULE_NOT_FOUND`, and `ERR_MODULE_NOT_FOUND` error codes silently, but rethrows any syntax or runtime errors encountered inside a present instrumentation file.

Sources: [packages/next/src/server/lib/router-utils/instrumentation-globals.external.ts:84-93](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/router-utils/instrumentation-globals.external.ts#L84-L93)

### Tracer Provider and Node Extensions

After successful registration, Node.js environment extensions modify OpenTelemetry tracer behaviors to integrate with React Server Components work units and cache components. The `afterRegistration()` function verifies that `process.env.NEXT_RUNTIME !== 'edge'` before invoking `extendTracerProviderForCacheComponents()`.

Sources: [packages/next/src/server/lib/router-utils/instrumentation-node-extensions.ts:18-26](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/router-utils/instrumentation-node-extensions.ts#L18-L26)

| Function / Component | Target Scope | Description | Sources |
| --- | --- | --- | --- |
| `afterRegistration()` | Node.js Runtime | Validates runtime is not edge and triggers tracer provider extensions. | [packages/next/src/server/lib/router-utils/instrumentation-node-extensions.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/router-utils/instrumentation-node-extensions.ts#L18-L26) |
| `isProxyTracerProvider()` | `TracerProvider` | Type guard checking for the presence of `getDelegateTracer`. | [packages/next/src/server/lib/router-utils/instrumentation-node-extensions.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/router-utils/instrumentation-node-extensions.ts#L28-L35) |
| `instrumentTracerForCacheComponents()` | `Tracer` | Patches `startSpan` and `startActiveSpan` to exit `workUnitAsyncStorage` during span creation. | [packages/next/src/server/lib/router-utils/instrumentation-node-extensions.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/router-utils/instrumentation-node-extensions.ts#L87-L146) |
| `ensureInstrumentationRegistered()` | `Promise<void>` | Memoizes `registerInstrumentation` to prevent concurrent multi-registration attempts. | [packages/next/src/server/lib/router-utils/instrumentation-globals.external.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/router-utils/instrumentation-globals.external.ts#L162-L174) |

> [!TIP]
> The tracer extension attempts to load user-supplied `@opentelemetry/api` packages first, falling back to Next.js's bundled dependency version if the external module resolution fails.

Sources: [packages/next/src/server/lib/router-utils/instrumentation-node-extensions.ts:52-57](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/router-utils/instrumentation-node-extensions.ts#L52-L57)

## Bundler Profiling and Tap Tracing

### Webpack and Rspack Compilation Hooks

The `ProfilingPlugin` and `RspackProfilingPlugin` capture build metrics by attaching performance measurement spans to compiler tap hooks. Both plugins register lifecycle listeners on compilation phases to trace module construction, chunk generation, asset emission, and loader execution.

```typescript
apply(compiler: any) {
  this.traceTopLevelHooks(compiler)
  this.traceCompilationHooks(compiler)
  this.compiler = compiler
}

traceHookPair(
  spanName: string | (() => string),
  startHook: any,
  stopHook: any,
  {
    parentSpan,
    attrs,
    onStart,
    onStop,
  }: {
    parentSpan?: (...params: any[]) => Span
    attrs?: any
    onStart?: (span: Span, ...params: any[]) => void
    onStop?: (span: Span, ...params: any[]) => void
  } = {}
) {
  let span: Span | undefined
  startHook.tap(
    { name: pluginName, stage: -Infinity },
    (...params: any[]) => {
      const name = typeof spanName === 'function' ? spanName() : spanName
      const attributes = attrs ? attrs(...params) : attrs
      span = parentSpan
        ? parentSpan(...params).traceChild(name, attributes)
        : this.runWebpackSpan.traceChild(name, attributes)

      if (onStart) onStart(span, ...params)
    }
  )
  stopHook.tap({ name: pluginName, stage: Infinity }, (...params: any[]) => {
    // `stopHook` may be triggered when `startHook` has not in cases
    // where `stopHook` is used as the terminating event for more
    // than one pair of hooks.
    if (!span) {
      return
    }

    if (onStop) onStop(span, ...params)
    span.stop()
  })
}
```
Sources: [packages/next/src/build/webpack/plugins/profiling-plugin.ts:43-89](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/profiling-plugin.ts#L43-L89)

| Plugin Name | Target Compiler | Key Lifecycle Hooks | Stored Maps / WeakMaps | Sources |
| --- | --- | --- | --- | --- |
| `ProfilingPlugin` | Webpack | `compilation`, `afterCompile`, `invalid`, `done`, `emit`, `afterEmit`, `make`, `buildModule`, `succeedModule`, `failedModule` | `spans`, `moduleSpansByCompilation`, `makeSpanByCompilation`, `sealSpanByCompilation`, `webpackInvalidSpans` | [packages/next/src/build/webpack/plugins/profiling-plugin.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/profiling-plugin.ts#L7-L14) |
| `RspackProfilingPlugin` | Rspack | `thisCompilation`, `loader`, `buildModule`, `succeedModule` | `moduleSpansByCompilation`, `compilationSpans` | [packages/next/src/build/webpack/plugins/rspack-profiling-plugin.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/rspack-profiling-plugin.ts#L8-L9) |

> [!WARNING]
> `ProfilingPlugin` registers `startHook` at `-Infinity` stage priority and `stopHook` at `Infinity` stage priority to ensure compilation boundaries encapsulate all intermediate plugin actions accurately.

Sources: [packages/next/src/build/webpack/plugins/profiling-plugin.ts:66-78](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/profiling-plugin.ts#L66-L78)

### Loader Span Profiling and Asset Traces

During module loading, both profiling plugins intercept the normal module loader hook to bind the active module span onto the Webpack or Rspack `loaderContext` under `currentTraceSpan`. This allows individual loaders to record custom child measurements inside the active module's lifetime.

```typescript
moduleHooks.loader.tap(
  pluginName,
  (loaderContext: any, module: any) => {
    const moduleSpan = moduleSpansByCompilation
      .get(compilation)
      ?.get(module)
    loaderContext.currentTraceSpan = moduleSpan
  }
)

compilation.hooks.succeedModule.tap(pluginName, (module: any) => {
  moduleSpansByCompilation?.get(compilation)?.get(module)?.stop()
})
```
Sources: [packages/next/src/build/webpack/plugins/profiling-plugin.ts:214-226](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/profiling-plugin.ts#L214-L226)

```typescript
moduleHooks.loader.tap(
  pluginName,
  (loaderContext: any, module: any) => {
    const moduleSpan = moduleSpansByCompilation
      .get(compilation)
      ?.get(module)
    loaderContext.currentTraceSpan = moduleSpan
  }
)
```
Sources: [packages/next/src/build/webpack/plugins/rspack-profiling-plugin.ts:35-43](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/rspack-profiling-plugin.ts#L35-L43)

```typescript
const TRACE_LABELS_SEAL = [
  'module assets',
  'create chunk assets',
  'asset render',
  'asset emit',
  'store asset',
]
```
Sources: [packages/next/src/build/webpack/plugins/profiling-plugin.ts:16-22](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/profiling-plugin.ts#L16-L22)

## Trace Export and Telemetry Reporting

### Telemetry Reporting and Event Access Lists

Next.js exports specific internal trace events to anonymous telemetry via `reportToTelemetry`, which inspects a `TRACE_EVENT_ACCESSLIST` mapping. When a recorded trace event name matches an entry in this access list and telemetry storage is active via `traceGlobals`, it records the event payload with its duration in microseconds.

| Trace Event Name | Telemetry Event Identifier | Sources |
| --- | --- | --- | --- |
| `webpack-invalidated` | `WEBPACK_INVALIDATED` | [packages/next/src/trace/report/to-telemetry.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/trace/report/to-telemetry.ts#L5-L9) |

Sources: [packages/next/src/trace/report/to-telemetry.ts:1-27](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/trace/report/to-telemetry.ts#L1-L27)

### Trace Uploader and Allowed Event Filters

The trace uploader processes session trace output files line-by-line, filtering events against predefined allowed sets depending on whether execution is in development or build mode. Root spans and feature flags prefixed with `feature.` are gathered to populate request metadata, including git branch, commit hash, environment detection flags, and system architecture parameters.

| Mode / Set Constant | Allowed Event Names / Prefixes | Sources |
| --- | --- | --- |
| `COMMON_ALLOWED_EVENTS` | `memory-usage`, `turbopack-persistence`, `turbopack-compaction`, `turbopack-module-graph`, `turbopack-write-entrypoints`, `turbopack-emit` | [packages/next/src/trace/trace-uploader.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/trace/trace-uploader.ts#L10-L17) |
| `DEV_ALLOWED_EVENTS` | `client-hmr-latency`, `render-path`, `hot-reloader`, `webpack-invalid-client`, `webpack-invalidated-server`, `navigation-to-hydration`, `start-dev-server`, `compile-path`, `memory-usage`, `server-restart-close-to-memory-threshold` plus common events | [packages/next/src/trace/trace-uploader.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/trace/trace-uploader.ts#L22-L34) |
| `BUILD_ALLOWED_EVENTS` | `next-build`, `run-turbopack`, `webpack-compilation`, `run-webpack-compiler`, `create-entrypoints`, `worker-main-edge-server`, `worker-main-client`, `worker-main-server`, `server`, `make`, `seal`, `chunk-graph`, `optimize-modules`, `optimize-chunks`, `optimize`, `optimize-tree`, `optimize-chunk-modules`, `module-hash`, `client`, `static-check`, `node-file-trace-build`, `static-generation`, `next-export`, `run-typescript`, `run-eslint` plus common events | [packages/next/src/trace/trace-uploader.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/trace/trace-uploader.ts#L36-L63) |

> [!WARNING]
> Setting `NEXT_TRACE_UPLOAD_FULL` bypasses event name filtering to upload the entire trace file, which may fail if the build generated an excessively large trace payload.

Sources: [packages/next/src/trace/trace-uploader.ts:65-75](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/trace/trace-uploader.ts#L65-L75)

### Turbopack Trace Server and MCP Integration

The CLI utility `startTurboTraceServerCli` loads native SWC bindings and starts a background trace server handle alongside a Model Context Protocol (MCP) server instance on designated ports (`wsPort` default 5747, `mcpPort` defaulting to `wsPort + 1`). The MCP server exposes a `query_spans` tool supporting parameter filtering, child enumeration, pagination, sorting options (`value`, `name`, `allocations`, `persistent-allocations`), and recursive subtree searches.

```typescript
export async function startTurboTraceServerCli(
  file: string,
  port: number | undefined,
  mcpPort: number | undefined
) {
  process.title = `next internal trace (v${process.env.__NEXT_VERSION})`
  const wsPort = port ?? DEFAULT_WS_PORT
  const httpPort = mcpPort ?? wsPort + 1

  let bindings
  try {
    bindings = await loadBindings()
  } catch {
    console.error(
      'Error: Could not load native bindings. The trace server requires native (non-WASM) bindings.'
    )
    process.exit(1)
  }

  let handle
  try {
    // Start the WebSocket trace server on a background thread (non-blocking).
    handle = bindings.turbo.startTurbopackTraceServerHandle(file, wsPort)
  } catch (err) {
    console.error(
      `Error: Could not start trace server for "${file}": ${err instanceof Error ? err.message : err}`
    )
    process.exit(1)
  }

  console.log(
    `Turbopack trace server started. View trace at https://trace.nextjs.org?port=${wsPort}`
  )

  // Create the MCP server.
  const mcpServer = new McpServer({
    name: 'Next.js Trace Server MCP',
    version: '0.1.0',
  })

  mcpServer.registerTool(
    'query_spans',
    {
      description: [
        'Query spans from a turbopack trace file: timing, CPU, attributes, allocation counters, and TurboMalloc live-memory samples.',
        '',
        'Navigation: pass a result `id` as `parent` to drill in. `search` is recursive over the whole subtree and returns full path IDs, so you can find a span without knowing where it lives. `depth` > 1 returns that many levels nested inline; `pageSize` (default 20, max 500) widens a page.',
        '',
        'Every response is paged: `totalCount` is how many matched in all, `totalPages` how many pages that is. Getting exactly `pageSize` results does not mean there are no more — check `totalCount`. Search cost scales with subtree size, so prefer scoping `parent` (or lowering `maxDepth`) over repeated root searches on a large trace.',
        '',
        'Allocations: `allocations` / `deallocations` / `allocationCount` and `persistentAllocations`, each with a `self*` counterpart excluding children. Comparing a total to its `self` shows whether a span allocates directly or only through descendants.',
        '',
        '`persistentAllocations` ranks allocators; it is NOT retained memory. It is allocated-minus-freed per TurboMalloc counters, which never see turbo-tasks cell or cache drops, so a total far above real peak RSS is expected rather than a leak. For absolute memory use `memorySummary` (count/start/end/min/peak/maxPressure, precomputed from `memorySamples`).',
        '',
        "But live heap is process-wide: a span's samples are just the global series sliced to its time range, so concurrent spans report identical memory however much each allocated. Rank concurrent work by the allocation fields, never by memory.",
        '',
        "Frees are charged to whichever span was on the stack at free time, not the one that allocated. A child with large `selfAllocations` under a parent with large `selfDeallocations` means the parent drops the child's arena — bounded, not leaking. Large `selfPersistentAllocations` with no such counterpart above it is the shape worth suspecting.",
        '',
        'For aggregated groups every allocation field is a group total, while `cpuDuration`, `correctedDuration` and `memorySamples` describe the example span only. `firstSpanId` is first in execution order; use `heaviestSpanId` to reach the member holding the most bytes.',
        '',
        'Set `outputType: "json"` for full precision and the raw `memorySamples` entries `[tsOffsetTicks, bytes, pressure, active_worker_threads]` (active_worker_threads excludes parked and blocking-pool threads); markdown is a human summary.',
      ].join('\n'),
      inputSchema: {
        parent: z
          .string()
          .optional()
          .describe(
            'Span ID to enumerate children of. Omit for root-level spans. Use the `id` field from a previous result.'
          ),
        aggregated: z
          .boolean()
          .optional()
          .describe(
            'When true (default), aggregate spans with the same name into a single entry. Set to false to see individual raw spans.'
          ),
        sort: z
          .enum(['value', 'name', 'allocations', 'persistent-allocations'])
          .optional()
          .describe(
            'Sort mode: "value" for corrected duration descending, "name" for alphabetical, "allocations" for total allocated bytes descending, "persistent-allocations" for `persistentAllocations` descending. Omit for execution order.'
```
Sources: [packages/next/src/cli/internal/turbo-trace-server.ts:169-255](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/cli/internal/turbo-trace-server.ts#L169-L255)

## Build File Tracing and Memory Sampling

### Output File Tracing and Dependency Collection

Next.js computes production file dependencies using Vercel’s `@vercel/nft` (`nodeFileTrace`) during the [build pipeline](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/build-system/build-pipeline). The trace coordination functions in `collectBuildTraces` process server entries, custom [incremental cache](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/server-runtime/incremental-cache) handlers, and middleware boundaries, while filtering out non-essential compilation assets and standalone development server modules.

| Artifact / Config Key | Target Path / Processing Rule | Sources |
| --- | --- | --- |
| `next-server.js.nft.json` | `distDir/next-server.js.nft.json` | [packages/next/src/build/collect-build-traces.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/collect-build-traces.ts#L127-L130) |
| `next-minimal-server.js.nft.json` | `distDir/next-minimal-server.js.nft.json` | [packages/next/src/build/collect-build-traces.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/collect-build-traces.ts#L131-L134) |
| `outputFileTracingExcludes` | Globs matching `next-server` added to `additionalIgnores` | [packages/next/src/build/collect-build-traces.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/collect-build-traces.ts#L215-L221) |
| Shared Ignore Patterns | `**/next/dist/compiled/next-server/**/*.dev.js`, `**/next/dist/compiled/jest-worker/**/*`, `**/next/dist/compiled/webpack/*`, `**/node_modules/webpack5/**/*` | [packages/next/src/build/collect-build-traces.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/collect-build-traces.ts#L223-L228) |

> [!NOTE]
> During standalone mode builds, extra IPC server files, startup wrappers, and requirement hooks are explicitly pushed into `sharedEntriesSet` to ensure complete runtime tracing outside the standard development environment.

Sources: [packages/next/src/build/collect-build-traces.ts:94-232](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/collect-build-traces.ts#L94-L232), [packages/next/src/build/utils.ts:1600-1635](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/utils.ts#L1600-L1635)

### Periodic V8 Memory Heap Tracing and Snapshots

The memory tracing subsystem tracks process memory footprint across process lifetimes by scheduling recurring snapshots via `startPeriodicMemoryUsageTracing` and logging metrics (`memory.rss`, `memory.heapUsed`, `memory.heapTotal`, `memory.heapMax`) into active OpenTelemetry spans or standard trace outputs.

| Metric Key | Description | Sources |
| --- | --- | --- |
| `memory.rss` | Resident Set Size obtained from `process.memoryUsage()` | [packages/next/src/lib/memory/trace.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/lib/memory/trace.ts#L15-L20) |
| `memory.heapUsed` | Used heap size obtained from `v8.getHeapStatistics()` | [packages/next/src/lib/memory/trace.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/lib/memory/trace.ts#L55-L60) |
| `memory.heapTotal` | Total heap size allocated | [packages/next/src/lib/memory/trace.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/lib/memory/trace.ts#L61-L63) |
| `memory.heapMax` | Maximum heap size limit (`heap_size_limit`) | [packages/next/src/lib/memory/trace.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/lib/memory/trace.ts#L63-L65) |

> [!WARNING]
> When `EXPERIMENTAL_DEBUG_MEMORY_USAGE` is enabled and heap utilization exceeds `HEAP_SNAPSHOT_THRESHOLD_PERCENT` (70%), Next.js automatically writes a V8 heap snapshot to `distDir` on the first occurrence, guarded by the `alreadyGeneratedHeapSnapshot` flag to prevent duplicate disk writes.

Sources: [packages/next/src/lib/memory/trace.ts:1-125](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/lib/memory/trace.ts#L1-L125)

## Related

- [Build Pipeline](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/build-system/build-pipeline)
- [Telemetry](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/cli-telemetry/telemetry)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/llms.txt) for all pages in this wiki.
