OpenTelemetry (OTEL) is basically a standard way to capture and send data about what your app is doing—things like traces, logs, and metrics—to tools like Signoz, Grafana, or whatever observability platform you prefer. The cool part is that OTEL is vendor-agnostic, so you're not tied down to one specific service. You can swap backends pretty easily.
#Setting Up OpenTelemetry for Next.js: Tracing, Logging, and Metrics
In this guide, I'll walk you through getting OpenTelemetry up and running in a Next.js project. We'll cover three key pieces of observability:
-
Tracing: capturing distributed traces for API requests and page navigations
-
Logging: collecting structured logs that tie back to those traces
-
Metrics: exporting performance data and custom app metrics
So why go with OpenTelemetry for Next.js? A few reasons stand out:
-
Built-in spans: Next.js automatically generates spans at the framework level
-
Exception tracking: errors get pulled into traces out of the box
-
Simplified setup: the
@vercel/otel
package takes care of the SDK, exporters, and instrumentation setup for you
By the time we're done, you'll have a solid observability setup—enough to catch issues before they become serious, dig into errors when they happen, and track down performance bottlenecks in the parts of your app that matter most.
#Installing OpenTelemetry in Next.js
While @vercel/otel
is a super convenient option and works great for a lot of projects, it doesn't give you the same
level of control as wiring up OpenTelemetry yourself. In this post, I'm going to walk through setting up OTEL from
scratch so you can see exactly how the pieces fit together—and along the way, I'll explain the different configuration
options you can tweak to match your needs.
In my setup, I'm using
@opentelemetry/sdk-node
, which only works with the Node.js runtime. If your app is running on the Edge runtime, you'll need a different approach. In that case, you can use@vercel/otel
, which automatically falls back to@opentelemetry/sdk-trace-web
(compatible with Edge), or roll your own setup that does something similar.
Alright, let's kick things off by pulling in the OpenTelemetry packages we'll need. Go ahead and install the required dependencies:
I've included specific versions for each package here to make sure everything in this tutorial works as expected. That said, you're free to update to the latest versions if you want—but using these exact versions helps avoid any unexpected issues from breaking changes in future major releases.
If your observability backend doesn't support GRPC there's almost always an equivalent HTTP exporter
available. For example instead of installing @opentelemetry/exporter-trace-otlp-grpc
you should install
@opentelemetry/exporter-trace-otlp-http
. Now that I think about it, that's probably the more common scenario. 😅
This install gives you everything you need to get started: the core OpenTelemetry API and SDK, built-in instrumentations for things like HTTP and Fetch calls, plus OTLP exporters so you can send both traces and metrics to your observability backend.
#Setting up the SDK
Create a new file instrumentation.node.ts
at the root of your Next.js project:
Create a new file instrumentation.ts
at the root of your Next.js project:
In Next.js, instrumentation.ts
(or .js) is a
special convention file. Next.js will automatically look for it at
the root of your project /instrumentation.ts
and run it when your application starts up.
Don't forget to add the following environment variables:
The default network port is 4317 for both OTLP/gRPC and OTLP/HTTP
#Configuring Tracing
Once the SDK is set up, your app will automatically start exporting telemetry data using the exporters you configured. But you might be wondering: what exactly gets captured if I haven't written any tracing code yet?
The good news is that Next.js already comes with built-in OpenTelemetry spans for key parts of the framework, like:
-
API routes (
pages/api
orapp/api
) -
Pages Router (when using the Pages Router)
-
App Router (when using the App Router)
Some of these spans are available out of the box, and if you want even more detail, you can enable verbose mode by setting:
#Default Spans in Next.js
When you enable OpenTelemetry (or use a setup that includes it), Next.js emits these spans out of the box:
Span | Description |
---|---|
[http.method] [next.route] | The root span for each incoming HTTP request. Tracks things like method, route pattern, status code. |
render route (app) [next.route] | Rendering of a route under the App Router. |
fetch [http.method] [http.url] | Any fetch call made during rendering etc. (client or server-side). |
executing api route (app) [next.route] | Execution of API route handlers in the App Router. |
getServerSideProps [next.route] | The execution of getServerSideProps in Pages Router. |
getStaticProps [next.route] | The execution of getStaticProps in Pages Router. |
render route (pages) [next.route] | Rendering logic in the Pages Router path. |
generateMetadata [next.page] | Creation of metadata for a page (e.g. when metadata API is used) in App Router. |
resolve page components | Resolving/loading page components during a route. |
resolve segment modules | Loading modules for layouts/pages/segments under the App Router. |
start response | A "zero-length" span fired when the response starts (first byte sent). |
See the Next.js docs for the full breakdown of default spans.
No matter if it's an API route, a page route, or an app route, you'll get a span for every request. Each span carries useful context—things like which route was hit, how long each part of the processing took, and any metadata from the HTTP request.
The real power shows up when you connect these automatic spans with custom spans and those provided by extra OpenTelemetry instrumentations. For example, imagine your app route's API method makes a call to another service (say, a Python backend). The trace will automatically capture how long that downstream request took and what status code it returned. At a glance, you can spot whether a slowdown is caused by your Next.js app itself or by an external service.
#Logging in OpenTelemetry
So far, we've focused on tracing, but OpenTelemetry also gives us a way to capture logs in a structured and consistent
way. Let's add a logger to our instrumentation.node.ts
file that we can use to emit custom messages.
This example creates a logger called nextjs-logger
and immediately sends a log entry when the application starts up.
Later, you can reuse this same logger throughout your code to emit logs at different severity levels (INFO
, WARN
,
ERROR
, etc.), all of which get correlated with your traces if your backend supports it.
You can use this logger directly in your code, or wrap it in a little helper function to make it easier to reuse across your app. Also worth noting: OpenTelemetry has logging instrumentations that can automatically hook into popular logging libraries like Winston or Pino. That way, you don't have to change how you log today—your existing logs can flow through OTEL and into your observability backend with almost no extra work.
#Capturing Exceptions with Spans
Sometimes it's not enough to just know a request happened—you also want to know when things go wrong. With OpenTelemetry, you can record exceptions directly on spans so they show up in your tracing backend.
Here's an example of modifying an API route to emit a custom span and capture an error:
This makes sure that the exception is tied to the trace, so you can visualize it alongside all the other spans in your observability backend.
#Using Next.js 15 onRequestError hook
Next.js 15 adds a new onRequestError
hook that lets you automatically capture server errors at the framework level.
You can add this to your instrumentation.ts
file to intercept all server actions and record errors:
This attaches the error to the active span for the request, giving you immediate visibility into what failed and the context around it.
#Conclusion
With the full suite of instrumentation configured, you'll start to see traces, logs, and metrics show up in your observability backend. This data is what makes troubleshooting and performance tuning so much easier.
You can visualize response times, error rates, and detailed error reports to quickly spot bottlenecks. For example, if a particular API route starts slowing down, you can drill into the traces to see exactly where the time is being spent.
Error rate metrics give you a real-time pulse on the health of your app. If failures start creeping up, you'll have the context and details needed—like request metadata and stack traces—to identify the root cause and fix it fast.
In short, integrating OpenTelemetry with your observability platform doesn't just give you raw data—it gives you actionable insights to monitor, troubleshoot, and continuously improve the performance and reliability of your Next.js application.