The Promise of Centralized Observability in Next.js

Next.js's App Router introduces a powerful new mechanism for integrating observability tooling: the instrumentation.ts file. This file, designed to run once when a server instance boots, is the ideal place to register tracing SDKs like OpenTelemetry. The goal is to provide developers with robust distributed tracing and centralized error reporting across their applications. Vercel's own @vercel/otel package simplifies this by wrapping the Node OpenTelemetry SDK, offering sensible defaults for exporters and span processors. This setup aims to drastically reduce the boilerplate typically associated with manual OpenTelemetry configurations, making it easier for developers to gain visibility into their application's performance and behavior.

The register() function exported from instrumentation.ts is crucial here. It executes only at server instance startup. This single execution is precisely why it's the correct location for global tracing SDK registration. However, this single-execution model also highlights a critical pitfall: any state managed at the module level, or any naive attempt to store a "current request" global, is inherently problematic when that server instance must handle multiple, concurrent requests.

Diagram illustrating Next.js App Router's instrumentation.ts and OpenTelemetry integration flow

The Core Problem: State Leakage Across Requests

The issue arises from Vercel's Fluid Compute architecture, which reuses warm server instances across concurrent requests. While this approach offers performance benefits by avoiding the overhead of spinning up a new instance for every incoming request, it creates a significant challenge for observability. If developers store request-specific data in module-level variables or simple global variables intended to represent the "current request," this state will persist between unrelated user requests that happen to land on the same warm instance. Consequently, traces that should be isolated to a single user's interaction become contaminated with data from previous requests. This leakage corrupts trace data, making it impossible to accurately diagnose issues for a specific user or request.

OpenTelemetry itself has mechanisms for context propagation, typically using asynchronous local storage or similar patterns within the Node.js runtime. These mechanisms are designed to keep trace context isolated to the specific asynchronous execution path of a request. The problem emerges when developers bypass or misunderstand these mechanisms, opting for simpler, but ultimately unsafe, global state management. A common mistake is to assume that a global variable will automatically reset or be instance-specific when it's not designed to be. In a serverless or server-instance-reuse environment, this assumption breaks down quickly.

onRequestError: A Hook with Potential Pitfalls

The instrumentation.ts file also provides hooks like onRequestError. This hook is intended to capture errors that occur during request handling. When an error is caught, this hook can be used to report it to an observability backend, potentially enriching the error report with contextual information from the request. However, the same state leakage problem that plagues tracing can also affect error reporting. If an error handler relies on global state that has been corrupted by a previous request, the error report might contain incorrect or irrelevant information, further complicating debugging.

Consider a scenario where a user performs an action that triggers an error. The error handler, expecting certain request-specific data to be present in a global variable, instead finds data from a completely different user's prior request. The resulting error log might point to the wrong user, the wrong session, or the wrong parameters, leading developers down a rabbit hole of misdiagnosis. This makes the onRequestError hook, while conceptually useful, a potential vector for propagating corrupted observability data if not used with extreme care regarding state management.

Mitigation and Best Practices

The primary mitigation strategy is to strictly adhere to OpenTelemetry's context propagation mechanisms. Developers must avoid using module-level variables or simple global variables to store request-scoped data. Instead, they should pass context explicitly or leverage asynchronous context management tools provided by OpenTelemetry or the underlying runtime. If @vercel/otel or other SDKs offer request-scoped context management, that should be the preferred approach.

For developers using Next.js App Router with the intention of implementing custom tracing or error handling, the field notes are clear: treat the instrumentation.ts file as a place for global SDK registration only. Any logic that depends on the specific state of an incoming request must be implemented within the request handler itself, or passed explicitly through the request lifecycle using context-aware patterns. This ensures that each trace and error report remains isolated and accurate, even in environments that reuse server instances.

The surprising detail here is not that state can leak in shared environments, but that a common pattern of using global variables for request context, often seen in older Node.js frameworks, can directly undermine the sophisticated tracing capabilities being introduced in Next.js. Developers accustomed to simpler server models might unknowingly introduce these critical bugs.