Monitoring Next.js with SigNoz offers several benefits. It provides real-time performance insights, allowing you to quickly identify and resolve issues. With comprehensive metrics on response times, error rates, and throughput, you can understand how your application behaves under different conditions. SigNoz also tracks user interactions to improve the overall user experience by identifying slow pages or problematic user flows. Additionally, it captures and logs errors, making it easier to diagnose and fix bugs. Insights into resource usage help optimize performance and reduce costs by pinpointing inefficient code or unnecessary resource consumption. Lastly, SigNoz aids in scalability analysis, ensuring your application can handle increased traffic effectively.
This guide will show you how to fully set up a Next.js project using the App Router with the ability to report errors to SigNoz. It covers both server and client components, so you can cherry-pick the steps based on your project setup.
Before reading this guide, we highly recommend reviewing the following:
Requirements
- Follow the error infrastructure setup
- Follow the instrumentation setup
- Have a SigNoz Cloud subscription or a self-hosted instance
- Be familiar with the SigNoz dashboard and query builder
Environment Variables Needed
NEXT_PUBLIC_SIGNOZ_ENDPOINT: The endpoint to send trace data toNEXT_PUBLIC_NODE_ENV: Defines the current runtime environment
Project Folder Structure
Ensure your project matches the following structure:
root
├── .env.local
├── next.config.js
├── tsconfig.json
├── package.json
├── package-lock.json
└── src
├── app
├── components
├── config
├── hooks
├── instrumentation
├── styles
├── utils
└── instrumentation.tsInstall OpenTelemetry Packages
We'll use OpenTelemetry to collect traces from our app:
npm install --save \
@opentelemetry/api@^1.9.0 \
@opentelemetry/auto-instrumentations-node@^0.56.0 \
@opentelemetry/auto-instrumentations-web@^0.45.1 \
@opentelemetry/context-zone@^2.0.0 \
@opentelemetry/exporter-jaeger@^1.30.1 \
@opentelemetry/exporter-metrics-otlp-http@^0.57.2 \
@opentelemetry/exporter-trace-otlp-http@^0.57.2 \
@opentelemetry/core@^1.30.1 \
@opentelemetry/instrumentation@^0.57.2 \
@opentelemetry/instrumentation-document-load@^0.44.1 \
@opentelemetry/instrumentation-fetch@^0.57.2 \
@opentelemetry/instrumentation-xml-http-request@^0.57.2 \
@opentelemetry/resources@^1.30.1 \
@opentelemetry/sdk-metrics@^1.30.1 \
@opentelemetry/sdk-node@^0.57.2 \
@opentelemetry/sdk-trace-base@^1.30.1 \
@opentelemetry/sdk-trace-web@^1.30.1 \
@opentelemetry/semantic-conventions@^1.30.0 \
@opentelemetry/winston-transport@^0.10.0These versions are verified for SigNoz. You can upgrade later if needed.
Server-Side Setup
instrumentation.node.ts
// src/instrumentation/instrumentation.node.ts
import process from "process";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { Resource } from "@opentelemetry/resources";
import { NodeSDK } from "@opentelemetry/sdk-node";
import {
ATTR_SERVICE_NAME,
SEMRESATTRS_DEPLOYMENT_ENVIRONMENT,
} from "@opentelemetry/semantic-conventions";
const exporterOptions = (endpoint: "traces" | "metrics") => ({
url: `${process.env.NEXT_PUBLIC_SIGNOZ_ENDPOINT}/${endpoint}`,
});
if (
!process.env.NEXT_PUBLIC_NODE_ENV ||
!process.env.NEXT_PUBLIC_SIGNOZ_ENDPOINT
) {
throw new Error("Missing env variables for instrumentation.");
}
const resource = new Resource({
[SEMRESATTRS_DEPLOYMENT_ENVIRONMENT]: process.env.NEXT_PUBLIC_NODE_ENV,
[ATTR_SERVICE_NAME]: "nextjs-apm-setup-demo",
});
const traceExporter = new OTLPTraceExporter(exporterOptions("traces"));
const sdk = new NodeSDK({
traceExporter,
instrumentations: [getNodeAutoInstrumentations()],
resource,
});
sdk.start();
process.on("SIGTERM", () => {
sdk
.shutdown()
.then(() => console.log("Tracing terminated"))
.catch((err) => console.error("Error terminating tracing", err))
.finally(() => process.exit(0));
});instrumentation.ts
// src/instrumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
await import("./instrumentation/instrumentation.node");
}
}Validation screenshots:
Client-Side Setup
Add global type declaration
// src/types/global.d.ts
import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
declare global {
interface Window {
__OTEL_PROVIDER__?: WebTracerProvider;
}
}
export {};instrumentation.browser.ts
// src/instrumentation/instrumentation.browser.ts
import { propagation } from "@opentelemetry/api";
import { ZoneContextManager } from "@opentelemetry/context-zone";
import {
CompositePropagator,
W3CTraceContextPropagator,
} from "@opentelemetry/core";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { registerInstrumentations } from "@opentelemetry/instrumentation";
import { DocumentLoadInstrumentation } from "@opentelemetry/instrumentation-document-load";
import { FetchInstrumentation } from "@opentelemetry/instrumentation-fetch";
import { XMLHttpRequestInstrumentation } from "@opentelemetry/instrumentation-xml-http-request";
import { Resource } from "@opentelemetry/resources";
import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
import {
ATTR_SERVICE_NAME,
SEMRESATTRS_DEPLOYMENT_ENVIRONMENT,
} from "@opentelemetry/semantic-conventions";
const appEnv = process.env.NEXT_PUBLIC_NODE_ENV || "development";
const sigNozEndpoint = process.env.NEXT_PUBLIC_SIGNOZ_ENDPOINT;
export function initBrowserTraceCollector() {
if (
typeof window === "undefined" ||
!appEnv ||
!sigNozEndpoint ||
window.__OTEL_PROVIDER__
) {
return;
}
const traceResource = new Resource({
[ATTR_SERVICE_NAME]: "nextjs-apm-setup-demo-browser",
[SEMRESATTRS_DEPLOYMENT_ENVIRONMENT]: appEnv,
application: "nextjs-apm-setup-demo-browser",
});
const traceExporter = new OTLPTraceExporter({
url: `${sigNozEndpoint}/traces`,
headers: {},
});
const traceProvider = new WebTracerProvider({ resource: traceResource });
traceProvider.addSpanProcessor(new SimpleSpanProcessor(traceExporter));
registerInstrumentations({
instrumentations: [
new DocumentLoadInstrumentation(),
new FetchInstrumentation({
clearTimingResources: true,
propagateTraceHeaderCorsUrls: [/.*/],
}),
new XMLHttpRequestInstrumentation({
clearTimingResources: true,
propagateTraceHeaderCorsUrls: [/.*/],
}),
],
});
traceProvider.register({
contextManager: new ZoneContextManager(),
});
propagation.setGlobalPropagator(
new CompositePropagator({
propagators: [new W3CTraceContextPropagator()],
})
);
window.__OTEL_PROVIDER__ = traceProvider;
console.log("Browser OpenTelemetry initialized");
}open-telemetry.tsx
// src/components/open-telemetry.tsx
"use client";
import { useEffect } from "react";
import { initBrowserTraceCollector } from "@instrumentation/instrumentation.browser";
export default function OpenTelemetry() {
useEffect(() => {
initBrowserTraceCollector();
}, []);
return null;
}Add to layout
// src/app/layout.tsx
import "../styles/app.css";
import { PropsWithChildren } from "react";
import dynamic from "next/dynamic";
const ErrorTracer = dynamic(() => import("@components/error-tracer"), {
ssr: false,
});
const OpenTelemetry = dynamic(() => import("@components/open-telemetry"), {
ssr: false,
});
export default async function Layout({ children }: PropsWithChildren) {
return (
<html className="h-full" lang="en">
<body className="min-h-full">
<ErrorTracer />
<OpenTelemetry />
{children}
</body>
</html>
);
}report-error-to-signoz.ts
// src/utils/report-error-to-signoz.ts
import { SpanStatusCode, trace } from "@opentelemetry/api";
export function reportErrorToSignoz(error: Error): void {
try {
if (!window?.__OTEL_PROVIDER__) {
console.warn("No tracer provider found.");
return;
}
const tracer = trace.getTracer("Error Boundary");
tracer.startActiveSpan("Error Boundary", (span) => {
span.recordException(error);
span.setAttribute("stackTrace", error.stack || "No stack trace");
span.setAttribute("errorMessage", error.message);
span.setAttribute("environment", "client");
span.setStatus({ code: SpanStatusCode.ERROR });
console.log("Reporting error to APM");
span.end();
});
} catch (err) {
console.error("Failed to report error:", err);
console.error("Original error:", error);
}
}Update error-tracer.tsx
// src/components/error-tracer.tsx
"use client";
import { reportErrorToSignoz } from "@utils/report-error-to-signoz";
import { useEffect } from "react";
export default function ErrorTracer() {
useEffect(() => {
const handleError = (event: ErrorEvent) => {
reportErrorToSignoz(event.error);
};
const handleUnhandledRejection = (event: PromiseRejectionEvent) => {
reportErrorToSignoz(event.reason);
};
window.addEventListener("error", handleError);
window.addEventListener("unhandledrejection", handleUnhandledRejection);
return () => {
window.removeEventListener("error", handleError);
window.removeEventListener("unhandledrejection", handleUnhandledRejection);
};
}, []);
return null;
}Validate
Conclusion
nextjs-error-reporting-setup-signoz folder.Written by

Engineering Team
Development
Our engineering team is a group of highly skilled and experienced software engineers with a passion for building high-quality web and mobile applications. They are dedicated to creating reliable, scalable, and user-friendly software solutions that meet the needs of our clients.

