Skip to main content
Observability is crucial for understanding the behavior and performance of your Next.js application. Next.js has built-in OpenTelemetry support—framework internals are already instrumented with spans. OpenTelemetry is platform-agnostic: you can switch observability providers without changing your code.

Getting started

The quickest way to add OpenTelemetry is with the @vercel/otel package.

Using @vercel/otel

1

Install packages

npm install @vercel/otel @opentelemetry/sdk-logs @opentelemetry/api-logs @opentelemetry/instrumentation
2

Create instrumentation.ts

Create an instrumentation.ts file in the root of your project (or in /src if you use that layout):
import { registerOTel } from '@vercel/otel'

export function register() {
  registerOTel({ serviceName: 'next-app' })
}
The instrumentation file must be in the project root, not inside app/ or pages/.

Manual configuration

For full control over the OpenTelemetry SDK, configure it manually. Because NodeSDK is not compatible with the Edge runtime, conditionally import it:
npm install @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/semantic-conventions @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-http
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await import('./instrumentation.node.ts')
  }
}
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { resourceFromAttributes } from '@opentelemetry/resources'
import { NodeSDK } from '@opentelemetry/sdk-node'
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node'
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'

const sdk = new NodeSDK({
  resource: resourceFromAttributes({
    [ATTR_SERVICE_NAME]: 'next-app',
  }),
  spanProcessor: new SimpleSpanProcessor(new OTLPTraceExporter()),
})
sdk.start()

Testing your instrumentation

Use the OpenTelemetry dev environment to test traces locally. When working correctly, you should see a root span labeled GET /requested/pathname with all other spans from that trace nested under it. To see additional spans:
NEXT_OTEL_VERBOSE=1 next dev

Custom spans

Add custom spans using the OpenTelemetry API:
npm install @opentelemetry/api
import { trace } from '@opentelemetry/api'

export async function fetchGithubStars() {
  return await trace
    .getTracer('nextjs-example')
    .startActiveSpan('fetchGithubStars', async (span) => {
      try {
        return await getValue()
      } finally {
        span.end()
      }
    })
}

Default spans

Next.js automatically instruments the following spans:
SpanTypeDescription
[http.method] [next.route]BaseServer.handleRequestRoot span for each incoming request
render route (app) [next.route]AppRender.getBodyResultRoute rendering in the App Router
fetch [http.method] [http.url]AppRender.fetchFetch request in your code
executing api route (app) [next.route]AppRouteRouteHandlers.runHandlerRoute Handler execution
generateMetadata [next.page]ResolveMetadata.generateMetadataMetadata generation for a page
resolve page componentsNextNodeServer.findPageComponentsPage component resolution
resolve segment modulesNextNodeServer.getLayoutOrPageModuleLayout or page module loading
start responseNextNodeServer.startResponseTime when first byte is sent
Span attributes follow OpenTelemetry semantic conventions plus custom next.* attributes:
  • next.span_name — Duplicates the span name
  • next.span_type — Unique identifier for the span type
  • next.route — Route pattern (e.g., /[param]/user)
  • next.rsc — Whether the request is an RSC prefetch request
  • next.page — Internal path to a special file (e.g., page.ts, layout.ts)
Set NEXT_OTEL_FETCH_DISABLED=1 to disable automatic instrumentation of fetch calls if you use a custom fetch instrumentation library.

Deployment

Vercel

OpenTelemetry works out of the box on Vercel. Follow the Vercel observability quickstart to connect to an observability provider.

Self-hosted

Spin up an OpenTelemetry Collector to receive and process telemetry from your Next.js app, then configure @vercel/otel or a custom exporter to send data to the collector.

Build docs developers (and LLMs) love