Instrument Next.js with OpenTelemetry

Send Next.js traces to Telemetry Machine with @vercel/otel and Next.js's built-in OpenTelemetry spans, for routes, rendering and fetch calls.

Open in Claude

Next.js traces its own routes, rendering and fetch calls once a tracer is registered. @vercel/otel registers one and exports its spans. It sends traces only.

Create the dataset

Create a traces dataset and an ingest key that can write to it, with tm-cli or the MCP server:

$ tm-cli datasets create --name shop-prod-traces --type traces
$ tm-cli api-keys create --name shop-web --dataset shop-prod-traces

tm-cli shows the key once, in api_key.api_key. Over MCP, your agent gets it once and saves it in your app's configuration. Or create the dataset in the app, which gives you a key for it.

Install

Terminal
$ npm install @vercel/otel @opentelemetry/api

Add instrumentation.ts to your project's root, or to src/ if your app is there:

instrumentation.ts
import { registerOTel } from '@vercel/otel';

export function register() {
  registerOTel();
}

Configure

Shell
export TM_INGEST_KEY='<ingest-key>'
export OTEL_SERVICE_NAME=shop-web
export OTEL_EXPORTER_OTLP_ENDPOINT=https://api.telemetrymachine.com
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_TRACES_HEADERS="X-API-Key=$TM_INGEST_KEY,X-Dataset-Name=shop-prod-traces"

Run

Build and start your app as usual:

Terminal
$ npm run build
$ npm start

npm run dev sends traces too.

Check it arrived

Send your app a few requests, then open Traces and choose shop-prod-traces. Each request is a server span named after its route, such as GET /items/[id], with Next.js's spans for routing and rendering beneath it.

Or count spans by name with tm-cli or the MCP server:

$ tm-cli query shop-prod-traces --since 15m --select "name, count(*)" --group-by name

Troubleshooting

  • Each request has an extra root span named just GET, with no http.route: @opentelemetry/auto-instrumentations-node is loaded as well, such as through NODE_OPTIONS. Remove it, and keep @vercel/otel alone.
  • Logs and metrics: @vercel/otel sends neither. Request rate, errors and latency are in the spans: count(*), status_code = 2 and duration_ns.