This guide shows you how to instrument your Next.js application with OpenTelemetry and send traces to SigNoz. You will use the @vercel/otel package for instrumentation of both server and edge runtime.
Prerequisites
- Node.js 18 or later
- Next.js 13.4+ (App Router or Pages Router)
- A SigNoz Cloud account or self-hosted SigNoz instance
Send traces to SigNoz
Step 1. Set environment variables
export OTEL_SERVICE_NAME="<service_name>"
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="https://ingest.<region>.signoz.cloud:443/v1/traces"
export SIGNOZ_INGESTION_KEY="<your-ingestion-key>"
Replace the following:
<service_name>: Name of your service (e.g.,nextjs-frontend).<region>: Your SigNoz Cloud region (us,eu, orin). See endpoints.<your-ingestion-key>: Your SigNoz ingestion key.
Step 2. Install OpenTelemetry packages
npm install --save @vercel/otel @opentelemetry/api
Step 3. Update next.config.mjs (Next.js 14 and below only)
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
instrumentationHook: true,
},
}
export default nextConfig
Step 4. Create the instrumentation file
Create instrumentation.ts (or instrumentation.js) in your project root or inside src/ if you use that folder structure. Do not place it inside app/ or pages/.
import { registerOTel, OTLPHttpJsonTraceExporter } from '@vercel/otel';
import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ERROR);
export function register() {
registerOTel({
serviceName: process.env.OTEL_SERVICE_NAME || 'nextjs-app',
traceExporter: new OTLPHttpJsonTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || 'https://ingest.us.signoz.cloud:443/v1/traces',
headers: process.env.SIGNOZ_INGESTION_KEY
? { 'signoz-ingestion-key': process.env.SIGNOZ_INGESTION_KEY }
: undefined,
}),
});
}
const { registerOTel, OTLPHttpJsonTraceExporter } = require('@vercel/otel');
const { diag, DiagConsoleLogger, DiagLogLevel } = require('@opentelemetry/api');
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ERROR);
exports.register = function register() {
registerOTel({
serviceName: process.env.OTEL_SERVICE_NAME || 'nextjs-app',
traceExporter: new OTLPHttpJsonTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || 'https://ingest.us.signoz.cloud:443/v1/traces',
headers: process.env.SIGNOZ_INGESTION_KEY
? { 'signoz-ingestion-key': process.env.SIGNOZ_INGESTION_KEY }
: undefined,
}),
});
};
Step 5. Run the application
npm run dev
Step 1. Set environment variables
Add the following environment variables to your Kubernetes deployment manifest:
env:
- name: OTEL_SERVICE_NAME
value: '<service-name>'
- name: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
value: 'https://ingest.<region>.signoz.cloud:443/v1/traces'
- name: SIGNOZ_INGESTION_KEY
value: '<your-ingestion-key>'
Replace the following:
<service-name>: Name of your service (e.g.,nextjs-frontend).<region>: Your SigNoz Cloud region (us,eu, orin). See endpoints.<your-ingestion-key>: Your SigNoz ingestion key.
Step 2. Install OpenTelemetry packages
npm install --save @vercel/otel @opentelemetry/api
Step 3. Create the instrumentation file
Create instrumentation.ts in your project root:
import { registerOTel, OTLPHttpJsonTraceExporter } from '@vercel/otel';
import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ERROR);
export function register() {
registerOTel({
serviceName: process.env.OTEL_SERVICE_NAME || 'nextjs-app',
traceExporter: new OTLPHttpJsonTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || 'https://ingest.us.signoz.cloud:443/v1/traces',
headers: process.env.SIGNOZ_INGESTION_KEY
? { 'signoz-ingestion-key': process.env.SIGNOZ_INGESTION_KEY }
: undefined,
}),
});
}
Step 4. Deploy and run
Deploy your application to Kubernetes. The environment variables will be injected into your container and picked up by the instrumentation file.
The OpenTelemetry Operator auto-injects instrumentation into your Next.js pods without modifying your application image.
Step 1. Set up the OpenTelemetry Operator
Install the Operator and Collector following the K8s OTel Operator installation guide.
Step 2. Create the Instrumentation resource
Create instrumentation.yaml to configure Node.js auto-instrumentation:
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: nodejs-instrumentation
spec:
exporter:
endpoint: http://otel-collector-collector:4318
propagators:
- tracecontext
- baggage
nodejs:
image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-nodejs:latest
Deploy this resource to your cluster.
Step 3. Add annotations to your deployment
Add these annotations to your pod template's metadata.annotations:
instrumentation.opentelemetry.io/inject-nodejs: "true"
Step 1. Set environment variables
$env:OTEL_SERVICE_NAME="<service_name>"
$env:OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="https://ingest.<region>.signoz.cloud:443/v1/traces"
$env:SIGNOZ_INGESTION_KEY="<your-ingestion-key>"
Replace the following:
<service_name>: Name of your service (e.g.,nextjs-frontend).<region>: Your SigNoz Cloud region (us,eu, orin). See endpoints.<your-ingestion-key>: Your SigNoz ingestion key.
Step 2. Install OpenTelemetry packages
npm install --save @vercel/otel @opentelemetry/api
Step 3. Update next.config.mjs (Next.js 14 and below only)
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
instrumentationHook: true,
},
}
export default nextConfig
Step 4. Create the instrumentation file
Create instrumentation.ts (or instrumentation.js) in your project root or inside src/ if you use that folder structure. Do not place it inside app/ or pages/.
import { registerOTel, OTLPHttpJsonTraceExporter } from '@vercel/otel';
import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ERROR);
export function register() {
registerOTel({
serviceName: process.env.OTEL_SERVICE_NAME || 'nextjs-app',
traceExporter: new OTLPHttpJsonTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || 'https://ingest.us.signoz.cloud:443/v1/traces',
headers: process.env.SIGNOZ_INGESTION_KEY
? { 'signoz-ingestion-key': process.env.SIGNOZ_INGESTION_KEY }
: undefined,
}),
});
}
const { registerOTel, OTLPHttpJsonTraceExporter } = require('@vercel/otel');
const { diag, DiagConsoleLogger, DiagLogLevel } = require('@opentelemetry/api');
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ERROR);
exports.register = function register() {
registerOTel({
serviceName: process.env.OTEL_SERVICE_NAME || 'nextjs-app',
traceExporter: new OTLPHttpJsonTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || 'https://ingest.us.signoz.cloud:443/v1/traces',
headers: process.env.SIGNOZ_INGESTION_KEY
? { 'signoz-ingestion-key': process.env.SIGNOZ_INGESTION_KEY }
: undefined,
}),
});
};
Step 5. Run the application
npm run dev
Step 1. Install OpenTelemetry packages
Add the packages to your package.json:
npm install --save @vercel/otel @opentelemetry/api
Step 2. Create the instrumentation file
Create instrumentation.ts in your project root:
import { registerOTel, OTLPHttpJsonTraceExporter } from '@vercel/otel';
import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ERROR);
export function register() {
registerOTel({
serviceName: process.env.OTEL_SERVICE_NAME || 'nextjs-app',
traceExporter: new OTLPHttpJsonTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || 'https://ingest.us.signoz.cloud:443/v1/traces',
headers: process.env.SIGNOZ_INGESTION_KEY
? { 'signoz-ingestion-key': process.env.SIGNOZ_INGESTION_KEY }
: undefined,
}),
});
}
Step 3. Configure your Dockerfile
FROM node:20-alpine AS base
FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci
FROM base AS builder
WORKDIR /app
COPY /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
# OpenTelemetry configuration
ENV OTEL_SERVICE_NAME="nextjs-app"
ENV OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="https://ingest.<region>.signoz.cloud:443/v1/traces"
ENV SIGNOZ_INGESTION_KEY="<your-ingestion-key>"
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY /app/public ./public
COPY /app/.next/standalone ./
COPY /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]
Or pass environment variables at runtime:
docker run -e OTEL_SERVICE_NAME="nextjs-app" \
-e OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="https://ingest.<region>.signoz.cloud:443/v1/traces" \
-e SIGNOZ_INGESTION_KEY="<your-ingestion-key>" \
-p 3000:3000 your-nextjs-image:latest
Replace the following:
<region>: Your SigNoz Cloud region (us,eu, orin).<your-ingestion-key>: Your SigNoz ingestion key.
For Next.js 14 and below
Why is this required?
In Next.js 14 and earlier versions, the instrumentation feature was experimental and required explicit opt-in through a configuration flag.
How to enable it
Add the following to your next.config.mjs:
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
instrumentationHook: true,
},
}
export default nextConfig
What changed in Next.js 15?
In Next.js 15, the instrumentation.js API was promoted from experimental to stable. This means:
- The
experimental.instrumentationHookconfig option is no longer needed - You can simply create an
instrumentation.tsfile without any additional configuration - A new
onRequestErrorhook was introduced for better error tracking integration
Validate
After running your instrumented application, verify traces appear in SigNoz:
- Generate traffic by making requests to your application.
- Open SigNoz and navigate to Services.
- Click Refresh and look for your service name.
- Go to Traces and filter by your service to inspect spans.
Troubleshooting
Traces are not appearing in SigNoz
Check the instrumentation file location: The file must be in the project root or src/ folder, not inside app/ or pages/.
Enable debug logging: Change DiagLogLevel.ERROR to DiagLogLevel.DEBUG in your instrumentation file to see detailed output.
Verify endpoint connectivity:
curl -v https://ingest.<region>.signoz.cloud:443/v1/traces
Instrumentation hook not working (Next.js 14 and below)
Make sure instrumentationHook: true is set in next.config.mjs under experimental.
Edge runtime traces missing
The @vercel/otel package supports edge runtime. Ensure your instrumentation file exports a register function and that it runs in both Node.js and edge environments.
Service not appearing in Services tab
- Wait a few minutes for data to propagate
- Make actual requests to your app (not just health checks)
- Verify your ingestion key is correct
- Check that the region in the endpoint matches your SigNoz Cloud region
CORS errors when sending traces
If you see CORS errors, you may be accidentally trying to send traces from the browser. The @vercel/otel instrumentation only runs server-side. For browser-side tracing, see the frontend monitoring guide.
Setup OpenTelemetry Collector (Optional)
What is the OpenTelemetry Collector?
The OTel Collector acts as a middleman between your app and SigNoz. Instead of sending data directly, your application sends everything to the Collector first, which then forwards it along.
Why use it?
- Cleaning up data — Filter out noisy traces you don't care about, or remove sensitive info before it leaves your servers.
- Keeping your app lightweight — Let the Collector handle batching, retries, and compression instead of your application code.
- Adding context automatically — The Collector can tag your data with useful info like which Kubernetes pod or cloud region it came from.
- Future flexibility — Want to send data to multiple backends later? The Collector makes that easy without changing your app.
See Switch from direct export to Collector for step-by-step instructions.
Sample application
Check out the Sample Next.js App for a complete working example.