OpenTelemetry for Next.js
OpenTelemetry for Next.js uses a framework-specific setup — the @vercel/otel helper plus a dedicated instrumentation.ts file at your project root. It needs a tiny amount of setup code; no --require flag will work.
Like Go, Next.js requires explicit setup — there is no agent or auto-loader. Two architectural reasons:
- Next.js produces a standalone build that bundles only the imports it detects. The generic
--require @opentelemetry/auto-instrumentations-node/registerflag fails because the OTel packages get tree-shaken out. - Next.js exposes its own startup hook (
instrumentation.ts) designed specifically for telemetry — using it produces better trace quality (correct route names likeGET /products/[id], full coverage of server components).
The setup is a one-time job per app — once instrumentation.ts is in place, every route and server component traces automatically.
Before You Begin
You'll need:
- A Next.js application (Next.js 13.4+ for the
instrumentation.tshook) - The URL of your CodeXray collector (e.g.,
https://your-codexray.example.com) - A project API key from the CodeXray UI (Project Settings → API Keys)
Step 1 — Install the Required Packages
npm install \
@opentelemetry/api \
@opentelemetry/sdk-trace-base \
@vercel/otel
| Package | What it does |
|---|---|
@opentelemetry/api | The core OTel API — used to create spans, set attributes, and propagate context |
@opentelemetry/sdk-trace-base | The tracing SDK that processes and batches spans before export |
@vercel/otel | Next.js-aware helper that wires the SDK into the framework's startup hook — handles standalone-build quirks and server-component instrumentation |
Step 2 — Create instrumentation.ts
Create a file named instrumentation.ts at the project root (same level as next.config.js):
import { registerOTel } from "@vercel/otel";
import {
BatchSpanProcessor,
SimpleSpanProcessor,
ConsoleSpanExporter,
} from "@opentelemetry/sdk-trace-base";
import { OTLPHttpProtoTraceExporter } from "@vercel/otel";
export function register() {
registerOTel({
serviceName: process.env.OTEL_SERVICE_NAME || "your-service-name",
spanProcessors: [
// Console exporter — useful while you're verifying setup. Remove for production.
new SimpleSpanProcessor(new ConsoleSpanExporter()),
// OTLP exporter — ships spans to CodeXray
new BatchSpanProcessor(
new OTLPHttpProtoTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
headers: {
"x-api-key": process.env.OTEL_EXPORTER_OTLP_HEADERS_KEY,
},
})
),
],
});
}
What this does:
- Sets the service name (read from env, with a fallback)
- Wires a batched OTLP exporter that ships spans to your CodeXray collector
- Adds a console exporter so you can see spans in your logs while you verify setup — remove or guard with
if (process.env.NODE_ENV !== "production")before going live
The register() function is called automatically by Next.js when the server starts.
Step 3 — Enable Standalone Output
In next.config.js, set output: "standalone":
module.exports = {
output: "standalone",
// ... your existing config
};
Standalone mode produces a self-contained production build with only the files and dependencies your app actually uses — smaller images, faster cold starts. It also keeps instrumentation.ts in the build output, which is required for it to run at startup.
Step 4 — Configure via Environment Variables
export OTEL_SERVICE_NAME="your-service-name"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://your-codexray.example.com"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_EXPORTER_OTLP_HEADERS_KEY="your-project-api-key"
export OTEL_TRACES_EXPORTER="otlp"
| Variable | Purpose |
|---|---|
OTEL_SERVICE_NAME | The name that appears in CodeXray's Applications list |
OTEL_EXPORTER_OTLP_ENDPOINT | Your CodeXray collector URL (no trailing slash) |
OTEL_EXPORTER_OTLP_PROTOCOL | http/protobuf (recommended) |
OTEL_EXPORTER_OTLP_HEADERS_KEY | Authentication — your project API key (passed through to the x-api-key header by the code in instrumentation.ts) |
OTEL_RESOURCE_ATTRIBUTES | Optional — deployment.environment=production,service.version=1.3.0 |
For a Dockerfile, set these as ENV directives:
ENV OTEL_SERVICE_NAME="your-service-name"
ENV OTEL_EXPORTER_OTLP_ENDPOINT="https://your-codexray.example.com"
ENV OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
ENV OTEL_EXPORTER_OTLP_HEADERS_KEY="your-project-api-key"
ENV OTEL_TRACES_EXPORTER="otlp"
CMD ["node", "server.js"]
The CMD stays as node server.js — Next.js loads instrumentation.ts automatically. No --require flag, no NODE_OPTIONS.
Step 5 — Run Your Application
Build and start as you normally would:
npm run build
npm start
Or in Docker, docker run the image. The first request out of the gate produces a trace.
If You Already Use Sentry
Sentry ships its own OpenTelemetry initialiser, which will collide with instrumentation.ts. Disable Sentry's auto-OTel setup in sentry.server.config.ts:
Sentry.init({
// ... your existing Sentry config
skipOpenTelemetrySetup: true,
});
This keeps Sentry's error-reporting features intact while letting your instrumentation.ts own the tracing pipeline. Skip this step entirely if you're not using Sentry.
Verifying It's Working
- Deploy and make a request to any route in your app
- Check the server logs — the
ConsoleSpanExporteryou added in Step 2 should print spans for each request - In CodeXray, open Applications → find your service → Traces tab
- Traces should appear within a minute, named by HTTP method + route (e.g.
GET /api/users,GET /products/[id])
If you only see console spans but nothing in CodeXray, the OTLP exporter is failing — check the server logs for HTTP errors against the collector URL, and confirm the API key.
Custom Spans for Business Logic
The @vercel/otel setup gives you automatic spans for HTTP requests, server components, and Route Handlers. For your own business logic:
import { trace } from "@opentelemetry/api";
const tracer = trace.getTracer("my-service");
export async function processOrder(orderId: string) {
return tracer.startActiveSpan("processOrder", async (span) => {
span.setAttribute("order.id", orderId);
try {
// your business logic ...
} finally {
span.end();
}
});
}
The span nests inside the request span automatically — so it shows up under the right route in the trace viewer.
Next Steps
- OpenTelemetry for Node.js — for non-Next.js Node services, where
--requireauto-instrumentation works - Traces Overview — the full Traces tab walkthrough
- OpenTelemetry for PHP · for Go · for Java · for Python — instrument your other services
- eBPF-based Tracing — immediate coverage for services you can't modify