It's 2 AM. Your /api/checkout endpoint has been returning 500 errors for 47 minutes. Your external uptime monitor? It shows green. The health check endpoint returns 200 OK every 60 seconds, like nothing is wrong.

Meanwhile, 340 real checkout requests have failed. You find out the next morning from a Stripe webhook backlog and three angry customer emails.

This is the problem with external monitoring for Next.js APIs. It checks the front door. It doesn't know what's happening inside.

Why External Monitoring Falls Short for Next.js

Tools like Pingdom, UptimeRobot, and even Datadog synthetic checks work the same way: they send an HTTP request to a URL every N seconds and check if the response is 200. That's it.

For a Next.js application, this model has fundamental blind spots:

  • It only tests one endpoint at a time. If you have 15 API routes, you'd need 15 separate monitors, and most tools charge per check.
  • It misses intermittent failures. If your /api/payments fails for 3 out of every 100 requests due to a race condition, a ping every 60 seconds will never catch it.
  • It can't measure real latency. The round-trip time from Virginia to your Vercel deployment includes network latency, CDN caching, and edge routing. None of that reflects what your actual users experience.
  • It doesn't work behind firewalls. If your API is internal (B2B, microservices, admin dashboards), external pings literally can't reach it.
  • Serverless cold starts break measurements. A health check hitting a warm function measures 50ms. A real user hitting a cold function gets 1800ms. The monitor never sees the cold start.

External monitoring answers one question: "Is the server reachable?" Internal monitoring answers the question that actually matters: "Are real requests succeeding?"

The Next.js Instrumentation Hook: Built for This

Since Next.js 13.2, there's a first-party way to run code when your server starts: the instrumentation hook. You create a file called instrumentation.ts at the root of your project, and Next.js calls its register() function once during server initialization.

This is the official mechanism for observability. The Next.js docs explicitly recommend it for:

  • OpenTelemetry tracing
  • Error tracking (Sentry, Bugsnag)
  • Performance monitoring
  • Custom logging

Here's the minimal structure:

// instrumentation.ts (project root)
export async function register() {
  // This runs once when the server starts.
  // Perfect for initializing monitoring, tracing, or logging.
  console.log('Server initialized, monitoring active')
}

On Vercel, this runs once per serverless function instance. On a Node.js server, it runs once at startup. Either way, it's the right place to hook into the request lifecycle.

Building a Basic Request Monitor from Scratch

Before reaching for a tool, let's understand what internal monitoring looks like. Here's a minimal implementation using Next.js middleware to intercept every API request:

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const start = Date.now()
  const response = NextResponse.next()

  // Track timing
  const duration = Date.now() - start
  const path = request.nextUrl.pathname

  // Log every API request
  if (path.startsWith('/api/')) {
console.log(JSON.stringify({
  path,
  method: request.method,
  duration,
  timestamp: new Date().toISOString(),
}))
  }

  return response
}

export const config = {
  matcher: '/api/:path*',
}

This gives you structured logs for every API request. But it's just logging, no alerting, no aggregation, no dashboards. To make this production-ready, you'd need to:

  1. Store metrics somewhere (time-series database)
  2. Calculate P50/P95/P99 latency percentiles
  3. Track error rates per endpoint
  4. Build an alerting system (Slack, email, etc.)
  5. Handle the infrastructure to run all of this

That's months of work. And you're trying to ship a product, not build a monitoring platform.

The Proper Approach: Instrumentation + Hooks

A better architecture uses the instrumentation hook to initialize monitoring and then captures data at the right level. Here's what a production setup looks like conceptually:

// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
// Only run on the server, not during build
const { setupMonitoring } = await import('./lib/monitoring')
setupMonitoring({
  onRequest: (data) => {
    // Called for every API request with:
    // - path, method, statusCode
    // - duration (ms)
    // - error (if any)
  },
  onError: (error, context) => {
    // Called when an unhandled error occurs
    // - error: the Error object
    // - context: { path, method, headers }
  },
})
  }
}
// lib/monitoring.ts
interface RequestData {
  path: string
  method: string
  statusCode: number
  duration: number
  error?: Error
}

interface MonitoringConfig {
  onRequest: (data: RequestData) => void
  onError: (error: Error, context: Record<string, unknown>) => void
}

export function setupMonitoring(config: MonitoringConfig) {
  // Patch the global fetch or use Node.js diagnostics_channel
  // to intercept HTTP handling at the server level.
  //
  // This is where the complexity lives:
  // - Hook into Next.js request handling
  // - Measure actual response times
  // - Capture errors without swallowing them
  // - Buffer and batch metrics for efficiency
}

The challenge is that "hook into Next.js request handling" is non-trivial. Next.js doesn't expose a simple middleware for server-side timing that includes the full response lifecycle. You'd need to patch Node.js HTTP internals or use diagnostics_channelboth of which require deep knowledge of Next.js internals and break across versions.

External vs Internal Monitoring: Side by Side

CapabilityExternal (Pingdom, UptimeRobot)Internal (Instrumentation Hook)
Monitors all API routes automaticallyNo, one monitor per URLYes, intercepts every request
Catches intermittent errorsNo, samples every N secondsYes, sees every request
Measures real user latencyNo, measures synthetic round-tripYes, measures server processing time
Works behind firewalls / VPNsNoYes
Captures cold start impactNo, hits warm instancesYes, measures every invocation
Detects errors in under 10 secondsNo, 1-5 minute check intervalsYes, real-time
Requires infrastructure to manageNo (SaaS)Depends, DIY requires infra
Setup complexityLow, paste a URLHigh (DIY) or Low (SDK)

The bottom line: external monitoring is a smoke detector in the hallway. Internal monitoring is a sensor in every room.

The Practical Shortcut: OpenTelemetry in 5 Lines

You don't have to patch Node.js internals yourself. Next.js has built-in OpenTelemetry support: once you register an OTel provider in the instrumentation hook, Next.js emits spans for incoming requests and route handlers inside your server, not as an external ping.

Install Vercel's OTel wrapper (it works outside Vercel too; check the Next.js OpenTelemetry guide for the current peer dependencies):

npm install @vercel/otel

Register it in your instrumentation file:

// instrumentation.ts
import { registerOTel } from '@vercel/otel'

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

From there, you export the traces to any OTLP-compatible backend, self-hosted or SaaS. What you get:

  • Every route covered: Requests to your API routes are traced as they happen. No URL list to maintain.
  • Real metrics: Response time, status codes, and errors for every endpoint, measured on real requests.
  • Alerts: Configure them in your backend (Slack, email, etc.) on error rate or latency per route.
  • No vendor lock-in: OTel is an open standard, so you can switch backends without re-instrumenting.

What to Track Per Route

For every API route, aim for:

  • P50 / P95 / P99 latencyreal server-side timing, not synthetic
  • Error ratepercentage of 4xx and 5xx responses
  • Throughputrequests per minute per endpoint
  • Cold start frequencyhow often functions initialize (Vercel)
  • Slow endpoint alertsa notification when P95 exceeds your threshold

Make alerts configurable per endpoint. You might want a Slack message when /api/checkout errors, but only an email digest for /api/analytics.

When to Use External Monitoring Too

Internal monitoring doesn't replace external monitoring, it complements it. You still want external checks for:

  • DNS resolutionverifying your domain resolves correctly from different regions
  • SSL certificate expirycatching certificates before they expire
  • Full outagesif your server is completely down, internal monitoring can't report (because it's down too)
  • Public status pagesshowing uptime to customers requires external verification

The ideal setup: internal instrumentation for request-level monitoring and alerts, plus a simple external uptime check (even a free UptimeRobot ping) for total outage detection.

Getting Started

  1. Run npm install @vercel/otel
  2. Add the 5 lines to instrumentation.ts
  3. Point the OTLP exporter at the backend you chose
  4. Deploy your app
  5. Build per-route P95 and error-rate alerts, and add one external uptime check for total outages

With internal traces plus one external check, your /api/checkout at 2 AM will never fail silently again.

Related Articles