OpenTelemetry tracing for Nuxt applications using Diagnostic Channels.
- πͺ‘ Built on top of Node.js Diagnostic Channels for zero-overhead instrumentation
- π― Automatic tracing for Nuxt renders, H3 requests, server routes, and Unstorage operations
- π Proper context propagation across async boundaries using OpenTelemetry AsyncLocalStorage
- π Lightweight β minimal overhead, no application code changes required
Instrumenting Nuxt applications with OpenTelemetry traditionally requires manual span creation and management. Diagnostic Channels provide a native Node.js mechanism for hooking into internal operations with minimal overhead.
Nuxt OTEL leverages these channels to automatically create OpenTelemetry spans for key Nuxt internals β renders, API requests, server route handling, and storage operations β giving you deep observability with zero application code changes.
Install the module:
pnpm i @lutejka/nuxt-otelThen add it to the modules array in your Nuxt config:
export default defineNuxtConfig({
modules: ['@lutejka/nuxt-otel'],
})export default defineNuxtConfig({
nuxtOtel: {
// Enable built-in OpenTelemetry SDK (Node.js only)
instrument: true,
// Enable DevTools UI at /__nuxt-otel
devtools: true,
},
})The tracing channels themselves are configured via the top-level tracingChannel Nuxt option:
export default defineNuxtConfig({
tracingChannel: {
nuxt: true, // Nuxt render lifecycle
h3: true, // H3 request handling
srvx: true, // Server route requests
unstorage: true, // Storage operations
},
})Set tracingChannel: true to enable all channels, or false to disable all.
Which instrumentation path is used depends on your Nitro preset.
For node-server (or node_server/nodeServer), bun, or the default preset, the module adds a built-in NodeSDK instance that exports traces via OTLP. The instrumentation can be configured via OTel SDK environment variables:
| Variable | Description |
|---|---|
OTEL_TRACES_EXPORTER |
Trace exporter (otlp, console, none) |
OTEL_METRICS_EXPORTER |
Metrics exporter (otlp, console, prometheus, none) |
OTEL_LOGS_EXPORTER |
Logs exporter (otlp, console, none) |
For presets not listed above, or when you need full control, disable the built-in setup and provide your own:
// nuxt.config.ts
export default defineNuxtConfig({
nuxtOtel: {
instrument: false,
},
})// server/plugins/otel.ts
import { defineNitroPlugin } from 'nitropack/runtime'
import { NodeSDK } from '@opentelemetry/sdk-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { Resource } from '@opentelemetry/resources'
import { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions'
export default defineNitroPlugin(() => {
const sdk = new NodeSDK({
resource: new Resource({
[SEMRESATTRS_SERVICE_NAME]: 'my-nuxt-app',
}),
traceExporter: new OTLPTraceExporter({
url: 'http://my-collector:4318/v1/traces',
}),
})
sdk.start()
})You can use any exporter (Console, Jaeger, Zipkin) or add auto-instrumentations like @opentelemetry/instrumentation-http.
The module provides two composables for manual instrumentation in your server routes.
Creates a tracer that wraps async operations in OpenTelemetry spans.
import { defineEventHandler } from 'h3'
export default defineEventHandler(async () => {
const { trace } = useOtelTracer('checkout')
const result = await trace('process-payment', async () => {
// this runs inside an OpenTelemetry span named "checkout.process-payment"
const payment = await processPayment()
return payment
})
return result
})Returns an OpenTelemetry logger instance for emitting structured logs.
import { defineEventHandler } from 'h3'
export default defineEventHandler(async () => {
const logger = useOtelLogger()
logger.emit({
severityNumber: 9,
body: 'Payment processed',
attributes: { amount: 42, currency: 'USD' },
})
})Logs and spans created with these composables automatically appear in the DevTools UI when devtools are enabled.
| Channel | Events | Required Nuxt Version |
|---|---|---|
nuxt.render |
SSR renders | >= 4.5.0 |
nuxt.island |
Island component renders | >= 4.5.0 |
nuxt.data |
Data fetching operations | >= 4.5.0 |
nuxt.plugin |
Plugin initialization | >= 4.5.0 |
h3.request |
HTTP requests via h3 | >= 5.0.0 |
srvx.request |
Server route requests | >= 5.0.0 |
srvx.middleware |
Server route middleware | >= 5.0.0 |
unstorage.* |
Storage operations (get, set, remove, etc.) | >= 5.0.0 |
Each traced operation follows the Diagnostic Channels lifecycle:
- Start: When an operation begins, a span is created with contextual attributes (URL, method, component name, etc.)
- Async Context: The span is bound to the current async context via
AsyncLocalStorage, ensuring child spans maintain parent relationships - End: On completion, the span is finalized with response metadata (status code, etc.)
- Error: If the operation fails, the span records the exception and marks the status as error
When devtools are enabled (devtools: true in nuxtOtel config), the module provides a built-in client UI at /__nuxt-otel for browsing traces and logs in real time.
Traces shows a resizable split panel with a trace list on the left and a waterfall timeline with span details on the right.
Logs provides a scrollable list of log entries with expandable metadata.
When using a custom instrumentation setup (i.e. instrument: false), you can still forward spans and logs to the devtools UI by exporting them to the OTLP HTTP endpoint at:
{devServerUrl}/__nuxt-otel-ingest
The endpoint accepts both trace and log OTLP JSON payloads (/v1/traces and /v1/logs). This lets you use any OTel SDK or collector to push data into the devtools UI without relying on the built-in NodeSDK.
Note: The MCP integration is development-only. The OTEL tools query in-memory trace and log stores that are populated during development. In production builds, the OTEL-specific tools are not available.
The module exposes a built-in MCP server at /__nuxt-otel-mcp for querying OpenTelemetry data over the Model Context Protocol. This uses the @modelcontextprotocol/sdk directly with Streamable HTTP transport.
| Tool | Description |
|---|---|
get_traces |
Retrieve all collected OpenTelemetry traces, with optional limit |
get_trace |
Retrieve a single trace and its spans by trace_id |
get_spans |
Retrieve collected spans, optionally filtered by trace_id and limit |
get_logs |
Retrieve all collected OpenTelemetry logs, with optional limit |
Once the dev server is running, you can connect any MCP client to the endpoint:
http://localhost:3000/__nuxt-otel-mcp
For example, in Cursor or VS Code:
{
"mcpServers": {
"nuxt-otel": {
"url": "http://localhost:3000/__nuxt-otel-mcp"
}
}
}- π Explore the OpenTelemetry Documentation
Licensed under the MIT license.
Local development
# Install dependencies
pnpm install
# Generate type stubs
pnpm dev:prepare
# Develop with the playground
pnpm dev
# Build the playground
pnpm dev:build
# Run ESLint
pnpm lint
# Run Vitest
pnpm test
pnpm test:watch
# Release new version
pnpm release

