Skip to main content
Production Next.js · Blog 24

Next.js 16 Monitoring and Observability

Connect production logs, errors, metrics, traces, health checks, Web Vitals, workers, and infrastructure into one actionable view without leaking private data.

Next.js production observability system collecting logs, metrics, traces, errors, and health signals

In Blog #23, we introduced workers and background jobs. Once a production system includes web requests, PostgreSQL, Redis, and independent workers, you need visibility into what it is doing when something becomes slow or fails. Next.js monitoring turns those runtime signals into evidence for diagnosis and safe response.

Repository monitoring audit

This publishing repository is PHP/Apache, not the example Next.js application. It has no Sentry, OpenTelemetry SDK, Prometheus, Grafana, Loki, Datadog, New Relic, structured server logger, collector, health service, Docker runtime, or production telemetry backend. The downloadable starter uses floating next: latest, so no exact patch version can be confirmed. This guide therefore shows provider-neutral educational patterns and does not install a second or imaginary monitoring stack.

  1. Blog #20Docker + TraefikPublished
  2. Blog #21Compose StackPublished
  3. Blog #22RedisPublished
  4. Blog #23Jobs + QueuesPublished
  5. Blog #24ObservabilityCurrent

What Is Observability in Next.js?

Observability in Next.js is the ability to understand production behavior from correlated logs, metrics, traces, errors, health checks, and user-experience data. It helps operators answer what failed, where it failed, which users or services were affected, and whether a deployment, dependency, or traffic change caused the problem.

Diagram 1: Monitoring at a glanceUsers reach Next.js, which produces logs, metrics, traces, and error signals; an observability system correlates them into dashboards and actionable alerts.
UsersNext.jsLogsMetricsTracesErrorsDashboard + alerts

Monitoring vs Observability

Monitoring

Tracks known conditions: uptime, error rate, latency, capacity, certificate expiry, queue age, and defined service objectives. It asks whether a known boundary has been crossed.

Observability

Provides correlated context for investigation. It helps explain why one route, release, tenant class, database call, or worker became unhealthy—even when nobody predicted that exact failure.

Neither means “collect everything forever.” A useful system captures signals tied to decisions, limits cardinality and retention, removes private data, and assigns an owner to every alert.

Diagram 2: Known checks and open investigationMonitoring compares known indicators with thresholds, while observability connects evidence across services to investigate unexpected behavior.
MonitoringIs error rate above the objective?Known question
ObservabilityWhy did checkout slow only after release B?Correlated investigation

Logs, Metrics, Traces, and Errors

Logs record discrete events. Metrics aggregate measurements over time. Traces follow work across boundaries using spans. Error monitoring groups exceptions with release and runtime context. They become substantially more useful when they share stable service, environment, deployment, trace, and request identifiers.

Diagram 3: The three observability pillarsStructured logs explain events, metrics reveal trends, and traces show request paths; shared identifiers correlate all three with captured errors.
LogsWhat happened?
MetricsHow often and how much?
TracesWhere was time spent?
Production application emitting correlated log, metric, and distributed trace streams into one monitoring dashboard
Correlate before you collect more. Shared context turns separate signals into one production story.

Structured Logging

Production logs should be machine-readable events, not sentences assembled from user input. A stable JSON schema makes filtering and alerting predictable. Use UTC timestamps, normalized levels, event names, route templates rather than raw URLs, numeric duration, safe error categories, deployment version, service name, and correlation IDs.

lib/logger.ts — minimal provider-neutral shape
type LogLevel = 'info' | 'warn' | 'error'
type SafeLog = {
  level: LogLevel
  event: string
  requestId?: string
  traceId?: string
  route?: string
  method?: string
  status?: number
  durationMs?: number
  errorType?: string
}

export function writeLog(entry: SafeLog) {
  const record = { timestamp: new Date().toISOString(), service: 'web', ...entry }
  const line = JSON.stringify(record)
  if (entry.level === 'error') console.error(line)
  else if (entry.level === 'warn') console.warn(line)
  else console.info(line)
}

This wrapper is intentionally small and not a substitute for a reviewed logger. If the real project already uses Pino, Winston, a platform logger, or an error provider, extend that system instead of creating a competing pipeline.

Useful fields

  • Stable event and service names
  • Environment and immutable release ID
  • Request, trace, job, and safe account references
  • Route template, status, and duration
  • Dependency name and safe error category

Never log

  • Passwords, cookies, or authorization headers
  • Access, refresh, reset, or verification tokens
  • Database or Redis connection strings
  • Complete request bodies or private documents
  • Provider credentials and raw sensitive errors
Diagram 4: Safe structured loggingA redaction boundary keeps event names, timing, route templates, and correlation IDs while removing credentials, cookies, raw payloads, and private customer data.
Keepevent, route template, status, duration, release, request ID
Removesecrets, cookies, tokens, URLs with credentials, raw bodies

Logging Server Components, Actions, and Routes

Server Components can log unexpected data-source failures, but avoid logging during every render because caching, revalidation, pre-rendering, and retries can produce surprising volume. Server Actions should log safe mutation outcomes after authentication, authorization, and validation—not submitted secrets. Route Handlers are natural places to measure status and latency, but raw URL query strings may contain private data.

Route Handler timing pattern
export async function POST(request: Request) {
  const startedAt = performance.now()
  const requestId = crypto.randomUUID()
  let status = 500

  try {
    const response = await handleAuthorizedRequest(request, requestId)
    status = response.status
    return response
  } catch (error) {
    writeLog({ level: 'error', event: 'api.unexpected_error', requestId,
      route: '/api/reports', method: 'POST', errorType: getSafeErrorType(error) })
    throw error
  } finally {
    writeLog({ level: 'info', event: 'api.completed', requestId,
      route: '/api/reports', method: 'POST', status,
      durationMs: Math.round(performance.now() - startedAt) })
  }
}

Expected and Unexpected Errors

Expected failures—invalid input, denied access, conflict, or a known provider rejection—should become deliberate safe responses and low-noise business metrics. Unexpected exceptions should reach the nearest Next.js error boundary or server error hook and the approved error provider. Do not report every 404 or validation issue as a production incident.

Diagram 5: Error classificationExpected operational outcomes become safe responses and counters; unexpected exceptions are captured with release and trace context, then surfaced without exposing internals to users.
ExpectedValidate → safe 4xx → metric
UnexpectedCapture → correlate → generic 5xx

Request IDs and Correlation IDs

A request ID identifies one inbound request. A trace ID connects spans across services. A job ID follows asynchronous work. Generate or accept identifiers only at a trusted boundary, validate their format and length, and do not use a user ID as a metric label. Propagate correlation context to supported HTTP clients, database instrumentation, and jobs without treating it as authorization.

Diagram 6: Correlation across web and worker pathsOne trace connects the gateway, Next.js route, PostgreSQL call, queued job, worker, and provider while each component emits its own safe logs and timing spans.
GatewayNext.jsPostgreSQLQueueWorkerProvider span

Next.js 16 Instrumentation

Current Next.js uses a root-level instrumentation.ts, or the corresponding location inside src. Its register function runs once when a server instance starts and must finish before requests are served. Because registration runs across runtimes, conditionally import Node- or Edge-specific packages. The optional onRequestError hook reports server errors and must await asynchronous provider work.

instrumentation.ts
import type { Instrumentation } from 'next'

export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await import('./instrumentation-node')
  }
}

export const onRequestError: Instrumentation.onRequestError = async (
  error, _request, context,
) => {
  await reportServerError({
    error,
    routeType: context.routeType,
    routePath: context.routePath,
  })
}

Do not log request headers or bodies from this global hook. Provider failure must not recursively crash error handling. Keep initialization deterministic, runtime-compatible, and fast.

OpenTelemetry with Next.js

OpenTelemetry is a vendor-neutral framework for producing, collecting, processing, and exporting telemetry. An SDK instruments the application, context propagation connects spans, and an exporter sends data—often with OTLP—to an OpenTelemetry Collector. The collector can batch, filter, redact, enrich, and route signals to approved backends. OpenTelemetry is not a dashboard or storage system by itself.

instrumentation.ts — official helper pattern
import { registerOTel } from '@vercel/otel'

export function register() {
  registerOTel('next-app')
}

The repository does not contain @vercel/otel, so this is not installed here. A real rollout must pin compatible packages, configure exporters and sampling, test Node versus Edge behavior, protect the collector endpoint, and measure telemetry overhead.

Diagram 7: OpenTelemetry pipelineInstrumented application services emit OTLP telemetry to a private collector, which batches, filters, enriches, and exports signals to an approved storage and visualization backend.
App SDKOTLPCollectorProcess + redactApproved backend

Traces and Spans

A trace describes one end-to-end operation; spans represent timed units such as a Route Handler, database query, Redis command, queue wait, worker task, or provider call. Add custom spans only around meaningful boundaries that automatic instrumentation cannot explain. Avoid attributes containing raw URLs, SQL text, user IDs, email addresses, prompt contents, or unbounded values.

Diagram 8: Distributed trace waterfallThe parent HTTP span contains authorization, database, enqueue, queue-wait, worker, and provider spans, revealing both execution time and asynchronous delay.
HTTP 420msAuth 18msDB 72msEnqueue 9msQueue wait 2.1sWorker 610ms

API Metrics and Cardinality

Measure request count, error count, and duration distributions by bounded attributes such as service, method, normalized route template, status class, region, and release. Use histograms for latency rather than averaging individual observations. User IDs, request IDs, raw paths, search strings, and error messages are high-cardinality labels that can exhaust memory and cost.

Request rateError rateLatency p95Saturation
Diagram 9: Safe metric dimensionsBounded labels such as route template, method, status class, and release aggregate efficiently; unique request, user, URL, and error values belong in sampled logs or traces instead.
Low cardinality/api/reports, POST, 2xx, release-42
High cardinalityrequest ID, email, raw URL, stack message

Core Web Vitals and Client Monitoring

Server health does not guarantee a fast user experience. Current Next.js supports useReportWebVitals in a small Client Component. Send LCP, CLS, INP, and supporting measurements to an approved internal endpoint or provider. Analyze field distributions by bounded page group, device class, and release; do not attach private URLs or identities.

app/_components/web-vitals.tsx
'use client'
import { useReportWebVitals } from 'next/web-vitals'

export function WebVitals() {
  useReportWebVitals((metric) => {
    const body = JSON.stringify({
      name: metric.name, value: metric.value, rating: metric.rating,
      id: metric.id, navigationType: metric.navigationType,
    })
    navigator.sendBeacon('/api/telemetry/web-vitals', body)
  })
  return null
}

The receiving route must enforce content type, body size, schema, origin policy, rate controls, and retention. Browser telemetry is untrusted input. Keep instrumentation-client.ts lightweight because it executes before hydration and can initialize approved early error or navigation monitoring.

Diagram 10: Real-user Web VitalsA tiny client boundary measures user experience, sends a bounded event to a protected telemetry route, and aggregates results by page group and release for dashboards.
BrowserWeb VitalsValidated endpointAggregateField dashboard

Health Checks: Liveness and Readiness

Liveness asks whether the process should be restarted. Readiness asks whether it should receive traffic. A liveness endpoint should be cheap and independent of remote services. Readiness may check only critical dependencies with strict timeouts and a small concurrency budget. Never expose versions, environment variables, hostnames, database details, or exception messages.

app/api/health/live/route.ts
export const dynamic = 'force-dynamic'
export function GET() {
  return Response.json(
    { status: 'ok' },
    { headers: { 'Cache-Control': 'no-store' } },
  )
}
Readiness shape
export async function GET() {
  const ready = await checkCriticalDependencies({ timeoutMs: 750 })
  return Response.json(
    { status: ready ? 'ready' : 'unavailable' },
    { status: ready ? 200 : 503, headers: { 'Cache-Control': 'no-store' } },
  )
}
Diagram 11: Liveness versus readinessLiveness checks the process without remote dependencies; readiness uses tightly bounded critical checks to add or remove an instance from traffic without exposing internals.
LivenessProcess responds → keep running
ReadinessCritical path healthy → receive traffic

Uptime and Synthetic Monitoring

An external probe should test DNS, TLS, routing, and a safe user-visible path from more than one location. A health endpoint alone can be green while login, checkout, asset delivery, or a regional dependency is broken. Keep synthetic accounts scoped, rotate credentials, prevent test activity from affecting analytics, and alert on sustained failures rather than one transient sample.

Docker, Traefik, PostgreSQL, and Redis

Docker and host

Track CPU throttling, memory working set and OOM kills, disk and inode pressure, network errors, file descriptors, restarts, health transitions, image release, and deployment events.

Traefik

Observe request rate, status classes, duration, upstream failures, retries, TLS certificate expiry, router/service labels, and access logs with query strings and sensitive headers removed.

PostgreSQL

Monitor availability, connection pool usage, query latency, locks, deadlocks, long transactions, replication and backup health, disk growth, and cache behavior. Do not log raw parameters or unrestricted SQL.

Redis

Monitor command latency, memory, fragmentation, evictions, expired keys, connections, blocked clients, persistence, replication, errors, cache usefulness, and queue-specific depth and age.

Diagram 12: Full-stack production signalsThe edge, application, worker, PostgreSQL, Redis, and host each expose different health and performance signals that share environment and release context.
Traefikrate, status, TLS
Next.jserrors, latency, spans
Workersage, retries, stalls
PostgreSQLpool, locks, queries
Redismemory, latency, eviction
HostCPU, memory, disk
Full production stack with gateway, Next.js containers, workers, Redis, PostgreSQL, telemetry collector, dashboards, and alerts
Observe every critical boundary. Application and infrastructure telemetry meet at a controlled collection layer.

Background Worker and Queue Monitoring

The queue architecture from Blog #23 needs process and business signals. Track queue depth, oldest waiting age, arrival and completion rates, active concurrency, duration percentiles, retries, exhausted jobs, stalled jobs, scheduled-job lag, worker restarts, graceful shutdown duration, Redis connections, provider limits, and successful business outcomes.

Diagram 13: Queue health and backpressureArrival rate, queue depth and age, worker throughput, retry rate, and downstream saturation reveal whether a temporary burst is draining or becoming an incident.
ArrivalsDepth + ageWorkersThroughputRetries / failures

Alerts and Service Objectives

Alert on user impact and sustained risk, not every exception. Start with availability, error ratio, latency, queue age, resource exhaustion, critical dependency failures, certificate expiry, backup failure, and failed deployment health. Combine symptoms when possible, add a duration window, route to an owned destination, and attach a runbook and dashboard link.

SignalUseful alertAvoid
AvailabilitySustained failed external probesOne isolated timeout
ErrorsError ratio above objective with trafficEvery expected 4xx
LatencyHigh percentile exceeds objectiveAverage latency alone
QueueOldest job age grows beyond promiseDepth without arrival context
CapacitySustained saturation and user impactOne CPU spike
Diagram 14: Actionable alert pathA service objective defines acceptable behavior; sustained breach creates one routed alert with ownership, context, a dashboard, and a tested runbook.
ObjectiveWindowed signalAlertOwnerRunbook

Dashboards That Answer Questions

A useful overview shows traffic, availability, error rate, latency percentiles, saturation, active release, and deploy events. Add focused dashboards for routes, workers, PostgreSQL, Redis, Web Vitals, and business outcomes. Every chart needs units, scope, aggregation, timezone, and a reason to exist. A wall of unrelated graphs is not observability.

Production Incident Workflow

  1. Confirm user impact and incident scope.
  2. Assign an incident owner and communication channel.
  3. Check recent deployments and configuration changes.
  4. Use overview metrics to locate the failing service or dependency.
  5. Follow a trace and correlated logs without exposing private data.
  6. Mitigate safely: rollback, disable a feature, reduce admission, or restore capacity.
  7. Verify recovery through user-facing and business signals.
  8. Preserve a timeline and write corrective actions with owners.
Diagram 15: Production incident loopDetection leads to triage, correlation, mitigation, verification, and a learning review whose actions improve code, monitors, dashboards, or runbooks.
DetectTriageCorrelateMitigateVerifyLearn

Common Monitoring Mistakes

Logging everything

Volume, cost, and privacy risk rise while useful events disappear in noise.

Secrets in telemetry

Logs, traces, errors, replays, and dashboards must be treated as sensitive systems.

High-cardinality metrics

User, request, and raw URL labels create unbounded time series.

Average-only latency

Averages hide slow-tail users; inspect distributions and percentiles.

Health check overload

Heavy dependency checks can create the outage they are supposed to detect.

Alerting every error

Noise trains responders to ignore real incidents. Alert on sustained impact.

No release correlation

Without immutable deployment identity, regressions are harder to isolate and roll back.

Dashboard without owner

Signals do not improve reliability unless someone responds and updates the system.

Next.js Monitoring Best Practices

  • Start from user journeys and service objectives.
  • Reuse the approved logger and monitoring provider.
  • Emit structured events with stable names and units.
  • Redact at collection and application boundaries.
  • Correlate logs, traces, errors, jobs, and releases.
  • Use bounded metric dimensions.
  • Measure distributions and high percentiles.
  • Keep instrumentation runtime-aware and lightweight.
  • Separate liveness from readiness.
  • Monitor external uptime and real-user experience.
  • Observe workers, PostgreSQL, Redis, proxy, and host.
  • Sample traces intentionally and review cost.
  • Attach alerts to owners and tested runbooks.
  • Record deploy events and rollback evidence.
  • Test telemetry failure and incident procedures.
Diagram 16: Observability maturity loopUser objectives define signals; signals drive dashboards and alerts; incidents reveal gaps; corrective actions improve instrumentation, reliability, and objectives.
Measure what mattersObjectives → signals → alerts
Learn and improveIncident → action → safer system

Frequently Asked Questions

What is Next.js monitoring?

Next.js monitoring collects safe production signals about availability, errors, latency, traffic, resource use, and user experience so operators can detect and diagnose problems.

What is observability in Next.js?

Observability is the ability to understand a Next.js system from its logs, metrics, traces, errors, health checks, and correlated infrastructure signals.

What is the difference between monitoring and observability?

Monitoring checks known conditions and thresholds. Observability provides enough correlated context to investigate both known and unexpected behavior.

Does Next.js support OpenTelemetry?

Yes. Next.js provides instrumentation hooks and OpenTelemetry guidance. The exact SDK, exporter, runtime support, sampling, and provider setup must match the deployed application.

Where does instrumentation.ts go in Next.js 16?

Place instrumentation.ts at the project root, or inside src when the app and pages directories are also under src. It is not placed inside app.

What does onRequestError do?

The optional onRequestError export receives server errors captured by Next.js so an application can await reporting to its selected monitoring provider.

How should I log Next.js requests?

Emit structured server logs with timestamp, level, event name, route template, method, status, duration, deployment version, and correlation identifiers while excluding secrets and private payloads.

What should never appear in production logs?

Never log passwords, cookies, authorization headers, access tokens, private keys, connection URLs, raw personal data, or complete sensitive request bodies.

How do I measure Core Web Vitals in Next.js?

Use a small Client Component with useReportWebVitals and send bounded metrics to an internal analytics endpoint or an approved monitoring provider.

What is a Next.js health check?

A health check is a narrow endpoint used by load balancers or orchestrators to determine whether the process is alive or ready for traffic without exposing internal details.

What is the difference between liveness and readiness?

Liveness indicates whether the process should be restarted. Readiness indicates whether it should currently receive traffic and may include strictly timed critical dependency checks.

Should health checks query the database?

A readiness check may perform a lightweight, tightly timed critical dependency check. Liveness normally should not depend on Redis, PostgreSQL, or external providers.

How do I monitor background workers?

Track process health, active concurrency, queue depth and age, throughput, duration, retries, stalled and failed jobs, dependency errors, and graceful shutdowns.

What alerts should a Next.js application have?

Start with user-impacting availability, sustained error rate, latency, queue age, dependency saturation, resource exhaustion, and failed deployment alerts tied to an owned response procedure.

Do I need Prometheus, Grafana, or Sentry?

Not automatically. Reuse the approved platform and add tools only after defining required signals, ownership, data handling, retention, cost, and operational responsibilities.

Current Official References

Next Steps

You now have a production observability model spanning Next.js requests, client performance, workers, queues, PostgreSQL, Redis, containers, and the public edge. Start with the system that already exists, define user-facing objectives, and add the smallest set of correlated signals that supports safe action.

WhatsApp