Frontend performance today goes far beyond page load time. In modern Next.js applications, user experience depends on multiple asynchronous API calls, dynamic rendering, and real-time interactions. This makes traditional error logging and synthetic checks insufficient.
This is where OpenTelemetry becomes essential.
OpenTelemetry enables end-to-end tracing from the browser to backend services, giving teams clear visibility into latency, failures, and performance bottlenecks from the user’s perspective.
Modern web applications require sophisticated OpenTelemetry monitoring to understand user experiences across complex frontend architectures. This guide walks through a production-ready OpenTelemetry frontend implementation for Next.js applications, breaking down each component with detailed explanations.
Why Frontend Observability Matters
Traditional frontend monitoring relied on basic error logging and synthetic uptime checks. Today’s SPAs (Single Page Applications) and progressive web apps demand deeper insights: How long did that API call really take from the user’s perspective? Which JavaScript bundle is causing layout shifts? Why is the checkout flow failing for users in specific regions?
The Complete Implementation
Here’s the production-grade OpenTelemetry implementation for frontend-side monitoring:
Code Breakdown: Imports and Dependencies
This OpenTelemetry implementation begins with six critical package imports that form the foundation of OpenTelemetry monitoring:
Configuring the OTLP Trace Exporter
const exporter = new OTLPTraceExporter({
url: process.env.NEXT_PUBLIC_SYGNOZ_URL,
headers: {},
});
Code Breakdown: Telemetry Export Configuration
The OTLPTraceExporter is the outbound gateway for your OpenTelemetry frontend data. This component:
URL Configuration: Uses NEXT_PUBLIC_ prefix to make the environment variable available to browser bundles. The endpoint typically points to your SigNoz collector or OpenTelemetry Collector instance.
Headers: Currently empty, but commonly used for:
- Authentication tokens (x-api-key)
- Tenant identification for multi-tenant OpenTelemetry monitoring setups
- Custom routing directives
Protocol: Despite the console log mentioning “gRPC,” this implementation actually uses HTTP/JSON or HTTP/protobuf via OTLP—browsers cannot natively execute gRPC due to HTTP/2 requirements.
Resource and Provider Setup
const provider = new WebTracerProvider({
resource: resourceFromAttributes({
'service.name': process.env.NEXT_PUBLIC_SYGNOZ_SERVICE_NAME,
}),
spanProcessors: [
new BatchSpanProcessor(exporter),
],
});
Code Breakdown: Tracer Provider Architecture
The WebTracerProvider is the central nervous system of your OpenTelemetry implementation:
Resource Attributes: The service.name attribute is mandatory for OpenTelemetry monitoring backends to identify and group traces. Additional attributes often include:
- service.version: Deployment version for regression analysis
- deployment.environment: production/staging/development
- host.name: Browser hostname (limited utility for SPAs)
BatchSpanProcessor: This processor implements intelligent buffering for OpenTelemetry frontend performance:
- Collects spans in memory (default: 2048 span buffer)
- Exports batches every 5 seconds or when the buffer fills
- Prevents network flooding from high-frequency user interactions
- Handles retry logic with exponential backoff for failed exports
Context Management Registration
provider.register({ contextManager: new ZoneContextManager() });
Code Breakdown: Async Context Propagation
The ZoneContextManager solves JavaScript’s fundamental challenge: maintaining trace context through the event loop. In OpenTelemetry monitoring:
Without Zone.js: Async operations (setTimeout, Promises, fetch callbacks) lose parent span references, creating disconnected trace fragments.
With Zone.js: Patches browser APIs to preserve context across:
- User interaction handlers (click, scroll, input)
- Network request callbacks
- Animation frames and timers
- React state updates and effects
This ensures your OpenTelemetry frontend traces show complete request flows from user click → API call → state update → re-render.
Automatic Instrumentation Registration
registerInstrumentations({
tracerProvider: provider,
instrumentations: [
new FetchInstrumentation({
ignoreUrls: [/v1\/traces/, /v1\/metrics/, /v1\/logs/, /signoz/i],
clearTimingResources: true,
}),
new XMLHttpRequestInstrumentation({
ignoreUrls: [/v1\/traces/, /signoz/i],
}),
],
});
Code Breakdown: Zero-Code Monitoring
This is where OpenTelemetry implementation delivers immediate value through automatic instrumentation:
FetchInstrumentation captures:
Request/response headers (respecting CORS)
Timing data (DNS, TLS, TTFB, download duration)
Status codes and error states
Request/response body sizes
ignoreUrls Configuration: Prevents infinite telemetry loops by excluding:
Self-referential trace export endpoints (/v1/traces)
SigNoz collector endpoints to avoid monitoring the monitor
Metrics and logs endpoints if separately configured
clearTimingResources: Removes Performance API entries after capture to prevent memory leaks in long-running SPAs.
XMLHttpRequestInstrumentation: Provides identical coverage for legacy code or libraries still using XHR (some upload libraries, older GraphQL clients).
Conclusion
Implementing OpenTelemetry on the frontend shifts monitoring from reactive debugging to real user-centric observability. Instead of fragmented logs, you gain trace-level visibility across clicks, API calls, and UI updates.
With proper tracer configuration and automatic instrumentation, your frontend becomes a measurable and optimizable part of your distributed system.
Interested in how observability supports next-generation AI systems? Follow insights from Exei, an agentic AI platform as we explore the intersection of telemetry, automation, and agentic AI
