// LOADING
// LOADING
// LOADING_ARTICLE
The difference between monitoring and observability is subtle but important. Monitoring tells you that something is wrong—a dashboard turns red, an alert fires. Observability lets you understand why—by exploring logs, traces, and metrics without deploying new instrumentation. For a production web app, you need both.
Logs are the narrative of what your application did. They're the first thing you reach for when investigating an incident. For a Next.js app, the default console.log output is fine for development but inadequate in production—it lacks structure, context, and severity levels.
Use a structured logging library like pino:
bashnpm install pino pino-pretty
ts// lib/logger.ts import pino from 'pino'; const logger = pino({ level: process.env.LOG_LEVEL ?? 'info', ...(process.env.NODE_ENV !== 'production' && { transport: { target: 'pino-pretty' }, }), }); export default logger;
ts// In a Route Handler import logger from '@/lib/logger'; export async function POST(req: Request) { const start = Date.now(); try { const body = await req.json(); // ... handle request logger.info({ duration: Date.now() - start, path: '/api/contact' }, 'request completed'); return Response.json({ ok: true }); } catch (err) { logger.error({ err, path: '/api/contact' }, 'request failed'); return Response.json({ error: 'Internal error' }, { status: 500 }); } }
JSON-structured logs are parseable by every log aggregator—Datadog, Grafana Loki, CloudWatch Logs, Papertrail—without custom parsing rules.
Metrics are numeric measurements sampled over time: request rate, error rate, latency percentiles, memory usage. Unlike logs (which record discrete events), metrics aggregate behavior across many requests.
For Next.js on Vercel, built-in Analytics covers Core Web Vitals. For self-hosted deployments, instrument with OpenTelemetry:
bashnpm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node
ts// instrumentation.ts (Next.js 13.4+ hooks into this automatically) export async function register() { if (process.env.NEXT_RUNTIME === 'nodejs') { const { NodeSDK } = await import('@opentelemetry/sdk-node'); const { getNodeAutoInstrumentations } = await import( '@opentelemetry/auto-instrumentations-node' ); const sdk = new NodeSDK({ instrumentations: [getNodeAutoInstrumentations()], }); sdk.start(); } }
OpenTelemetry auto-instrumentation captures HTTP request spans, database query timings, and more without manual wrapping.
Traces connect a single user request across every service it touches—Next.js app, database, external API, background job. Each hop is a "span" with timing, tags, and a parent-child relationship. When a request is slow, traces tell you exactly where the time went.
If your application architecture is growing, read The Future of Serverless Architecture for context on how distributed tracing fits into event-driven and serverless patterns.
Raw logs capture errors, but error tracking services provide grouping, deduplication, release correlation, and user context. Sentry is the most widely used option for Next.js:
bashnpm install @sentry/nextjs npx @sentry/wizard@latest -i nextjs
The wizard creates sentry.client.config.ts, sentry.server.config.ts, and sentry.edge.config.ts. Key settings:
ts// sentry.server.config.ts import * as Sentry from '@sentry/nextjs'; Sentry.init({ dsn: process.env.SENTRY_DSN, tracesSampleRate: 0.1, // sample 10% of transactions for performance data environment: process.env.NODE_ENV, release: process.env.VERCEL_GIT_COMMIT_SHA, });
Tying releases to git commits means every error in Sentry links back to the exact code that caused it. This makes the CI/CD pipeline more valuable—you can see which deploy introduced a new error cluster.
Error tracking only fires when users hit errors. Uptime monitoring checks your endpoints proactively:
For a health check endpoint in Next.js:
ts// app/api/health/route.ts import { NextResponse } from 'next/server'; import { connectDB } from '@/lib/db'; export async function GET() { try { await connectDB(); return NextResponse.json({ status: 'ok', timestamp: Date.now() }); } catch { return NextResponse.json( { status: 'error', timestamp: Date.now() }, { status: 503 } ); } }
This health check verifies the database connection—not just that the Node process is running. Your uptime monitor should hit /api/health, not just the homepage.
Lab performance (Lighthouse, PageSpeed Insights) measures a synthetic user on a controlled network. Real User Monitoring captures what actual visitors experience across varying devices, networks, and geographies.
Next.js supports reporting Web Vitals via reportWebVitals:
ts// app/layout.tsx — client component wrapping export function reportWebVitals(metric: NextWebVitalsMetric) { if (metric.label === 'web-vital') { // Send to your analytics endpoint fetch('/api/metrics', { method: 'POST', body: JSON.stringify(metric), }); } }
For what to do with those metrics and how they affect SEO, see Core Web Vitals in 2026.
Alerting is only useful if people respond to it. Alert fatigue—where pages are so frequent they get ignored—is worse than no alerting. Guidelines:
debug for verbose local diagnostics (disabled in production), info for normal operations, warn for expected-but-notable events, error for unexpected failures.For rate limiting and abuse prevention that generates useful alert signals, see Rate Limiting Your Next.js APIs.
A practical observability setup for a Next.js production app:
pino, shipped to a log aggregator/api/health endpointStart with error tracking and uptime monitoring—they cost the least time to set up and deliver the most immediate value. Add structured logging and distributed tracing as the application grows in complexity. You'll find issues before your users report them, which is the entire point. See the projects page to see these practices applied in real systems.