Skip to content
Back to Guides

How to set up OpenTelemetry in Next.js with @vercel/otel

@vercel/otel sets up OpenTelemetry tracing in Next.js from one instrumentation.ts file. Configure registerOTel, use Vercel trace drains and fix common problems.

The Interfere, Next.js and OpenTelemetry logos
LG

Levi Gans

·

OpenTelemetry

@vercel/otel is Vercel’s package for OpenTelemetry in Next.js. Next.js already creates spans for requests, renders, Route Handlers and fetch calls, and @vercel/otel sets up everything that sends them to a backend from one call in instrumentation.ts. Unlike a manual NodeSDK setup, it also runs on the Edge runtime and works with Vercel trace drains.

If you deploy to Vercel, turn on a trace drain. Without one, every function keeps running after its response while it exports spans, and Vercel bills that time.

Our Next.js SDK ships with @vercel/otel, so you can skip to the SDK setup.

Install @vercel/otel

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

Create instrumentation.ts in the project root, or in src next to app if you use a src folder. It must not go inside app or pages.

TypeScript
1import { registerOTel } from "@vercel/otel";23export function register() {4  registerOTel({ serviceName: "storefront" });5}

When instrumentation.ts runs

  • Once per server instance, not per request. On Vercel, that’s each function cold start. With fluid compute, one instance then serves many invocations.
  • Once per runtime. Edge middleware or a proxy gets its own call. Check process.env.NEXT_RUNTIME before loading runtime-specific code.
  • Outside any request. Spans created in register don’t join a request trace.

The same file can export onRequestError, which Next.js calls when the server catches an error in a Server Component, Route Handler, Server Action or proxy. For the browser, Next.js loads instrumentation-client.ts before hydration.

The registerOTel options you will use

OptionWhat it does
serviceNameNames the service. OTEL_SERVICE_NAME overrides it.
attributesAdds resource attributes to the defaults.
instrumentationConfigConfigures fetch tracing, such as propagateContextUrls and ignoreUrls.
propagatorsSets context propagation. "auto" keeps W3C Trace Context.
spanProcessorsAdds span processors. "auto" keeps the default export.
traceSamplerDecides which requests to trace. OTEL_TRACES_SAMPLER also sets it.
flushTimeoutMillisCaps the flush after each request. The default is 10,000 ms.

Turn on a Vercel trace drain

Without a drain, @vercel/otel exports spans from inside your function. After each request, it waits in Vercel’s waitUntil until your backend accepts the batch, for up to flushTimeoutMillis (10 seconds by default). Vercel bills that wait as function duration, so a slow backend makes every invocation longer. A trace drain hands the spans to Vercel instead.

Without a drainWith a trace drain
Who sends the spansYour function, over OTLPVercel, out of process
After the responseWaits for the export, up to 10 secondsNo network export to wait for
Billed function durationIncludes the exportDoes not include the export

The easiest way to get one is to connect Vercel in Interfere’s settings. Interfere sets up a trace drain and a log drain that send straight to Interfere, with nothing to configure in Vercel.

  • Drains need a Vercel Pro or Enterprise plan.
  • Keep "auto" in spanProcessors, or spans never reach the drain.
  • Custom spans from Edge functions don’t reach the drain, so export them directly if you need them.
  • With a log drain, logs from a traced request carry traceId and spanId.

Add Interfere to @vercel/otel

Interfere watches the traces, logs and sessions your Next.js app sends, and opens a problem when something goes wrong: an error, a slow route, a regression after a release, or users getting stuck. It triages each problem for you, finds the root cause and the release that introduced it, shows who it affects, and suggests a fix linked to your code. When the fix ships, it confirms that users recovered.

The recommended setup is Interfere’s Next.js SDK on its own. @interfere/next ships with @vercel/otel, so you don’t install or configure it yourself. Your whole instrumentation.ts is:

TypeScript
1export { onRequestError, register } from "@interfere/next/instrumentation";

With a Vercel trace drain, the SDK calls registerOTel for you and sends spans through the drain. Without one, it exports to Interfere directly, and on Vercel it caps each export at 2 seconds. It only needs your INTERFERE_PUBLIC_KEY, not the OTEL_EXPORTER_OTLP_* variables.

With the browser setup from the Next.js SDK docs, the same SDK also bundles:

  • Session replay, recorded when consent allows, linked to the server traces of each session.
  • Browser errors, failed requests and Web Vitals, such as LCP, INP and CLS.
  • Browser-to-server traces, with the session ID carried in W3C baggage, including on soft navigations.
  • Releases and source maps through the withInterfere() build plugin, so stack traces show your original code.

If you already call registerOTel

Keep your registerOTel call and add Interfere’s kit to it instead of also calling the SDK’s register():

TypeScript
1import { registerOTel } from "@vercel/otel";2import { buildInterfereOtelKit } from "@interfere/next/instrumentation";34export { onRequestError } from "@interfere/next/instrumentation";56export function register() {7  const kit = buildInterfereOtelKit({ serviceName: "storefront" });89  registerOTel({10    serviceName: "storefront",11    attributes: { ...kit?.resourceAttributes },12    ...(kit && { idGenerator: kit.idGenerator }),13    spanProcessors: ["auto", ...(kit?.spanProcessors ?? [])],14    logRecordProcessors: [...(kit?.logRecordProcessors ?? [])],15    propagators: [16      "auto",17      ...(kit ? [kit.traceContextPropagator, ...kit.propagators] : []),18    ],19  });2021  kit?.enableConsoleBridge();22  void kit?.fetchRemoteConfig();23}

The kit adds processors and propagators without registering a second provider. It returns null on the Edge runtime or without INTERFERE_PUBLIC_KEY, which the optional chaining handles. Keep the propagators in this order. The OpenTelemetry composition docs cover each kit field.

Send @vercel/otel traces to any OTLP backend

Without the Interfere SDK, @vercel/otel can export to any OTLP backend through the standard variables. To send traces straight to Interfere this way, create a workspace API key with the Send telemetry permission and set:

Bash
1OTEL_EXPORTER_OTLP_ENDPOINT="https://api.interfere.com/drain"2OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"3OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer%20${INTERFERE_API_KEY}"

@vercel/otel appends /v1/traces to the endpoint, and %20 encodes the space after Bearer. Use http/protobuf or http/json: with grpc, it falls back to a default exporter that sends to localhost and drops your headers. Don’t combine this with the Interfere SDK, or each span arrives twice.

Troubleshooting

What you seeWhat to check
instrumentation.ts runs in dev but not productionWith src/app, move the file to src/instrumentation.ts. A root-level file has gone missing from standalone output (#81050).
register runs two or three timesNext.js calls it once per runtime, so Edge middleware adds a call (#51450).
Your propagators or provider seem ignoredOpenTelemetry keeps the first global provider. A second vendor SDK that also sets one loses without an error.
The build fails with next-prerender-randomNext.js 16 prerendering can reject Math.random(), which default span IDs use. Pass an idGenerator that avoids it, such as the Interfere kit’s.
Two request spans after adding HttpInstrumentationNext.js and @vercel/otel already trace requests and fetch (#51233). Remove the extra instrumentation.
Outgoing fetch calls have no traceparent@vercel/otel only propagates to your deployment URLs. Add your API to instrumentationConfig.fetch.propagateContextUrls.
Many visitors share one traceA cached page can serve one render’s traceparent to every visitor. The Interfere kit’s traceContextPropagator leaves it out of prerendered and revalidated HTML.
Fewer Next.js spans than expectedSet NEXT_OTEL_VERBOSE=1.
Nothing arrives in InterfereWith the SDK, check that INTERFERE_PUBLIC_KEY is set at runtime. With OTLP variables, set OTEL_LOG_LEVEL=debug. A 401 means a wrong or expired key, or one without org:telemetry:write.

Next.js telemetry vs OpenTelemetry

“Next.js telemetry” can mean two things. This guide covers OpenTelemetry: traces and logs from your own app. Next.js also collects anonymous usage data about the framework itself. Run npx next telemetry disable, or set NEXT_TELEMETRY_DISABLED=1, to turn that off.

Set up Interfere to see your Next.js traces next to the errors and session replays they belong to.

Find and fix production bugs with Interfere

Interfere notices when your app breaks, explains why, and shows who it affects.

Set up Interfere