Levi Gans
@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
1npm install @vercel/otel @opentelemetry/api @opentelemetry/api-logs @opentelemetry/sdk-logs @opentelemetry/instrumentationCreate 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.
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_RUNTIMEbefore loading runtime-specific code. - Outside any request. Spans created in
registerdon’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
| Option | What it does |
|---|---|
serviceName | Names the service. OTEL_SERVICE_NAME overrides it. |
attributes | Adds resource attributes to the defaults. |
instrumentationConfig | Configures fetch tracing, such as propagateContextUrls and ignoreUrls. |
propagators | Sets context propagation. "auto" keeps W3C Trace Context. |
spanProcessors | Adds span processors. "auto" keeps the default export. |
traceSampler | Decides which requests to trace. OTEL_TRACES_SAMPLER also sets it. |
flushTimeoutMillis | Caps 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 drain | With a trace drain | |
|---|---|---|
| Who sends the spans | Your function, over OTLP | Vercel, out of process |
| After the response | Waits for the export, up to 10 seconds | No network export to wait for |
| Billed function duration | Includes the export | Does 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"inspanProcessors, 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
traceIdandspanId.
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:
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():
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:
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 see | What to check |
|---|---|
instrumentation.ts runs in dev but not production | With 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 times | Next.js calls it once per runtime, so Edge middleware adds a call (#51450). |
| Your propagators or provider seem ignored | OpenTelemetry keeps the first global provider. A second vendor SDK that also sets one loses without an error. |
The build fails with next-prerender-random | Next.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 HttpInstrumentation | Next.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 trace | A 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 expected | Set NEXT_OTEL_VERBOSE=1. |
| Nothing arrives in Interfere | With 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.
