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.
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-tracescreate_dataset { "name": "shop-prod-traces", "signal_type": "traces" }
create_ingest_key { "name": "shop-web", "datasets": ["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
$ npm install @vercel/otel @opentelemetry/apiAdd instrumentation.ts to your project's root, or to src/ if your app is there:
import { registerOTel } from '@vercel/otel';
export function register() {
registerOTel();
}Configure
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:
$ npm run build
$ npm startnpm 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 namerun_query {
"dataset": "shop-prod-traces",
"description": "Spans by name",
"since": "15m",
"select": "name, count(*)",
"group_by": "name"
}Troubleshooting
- Each request has an extra root span named just
GET, with nohttp.route:@opentelemetry/auto-instrumentations-nodeis loaded as well, such as throughNODE_OPTIONS. Remove it, and keep@vercel/otelalone. - Logs and metrics:
@vercel/otelsends neither. Request rate, errors and latency are in the spans:count(*),status_code = 2andduration_ns.