Framework Guide

Next.js Monitoring

Set up health check endpoints and uptime monitoring for your Next.js application. Works with both Pages Router and App Router.

Why Monitor Your Next.js App?

Next.js applications run as serverless functions on platforms like Vercel, or as Node.js servers on custom infrastructure. Either way, they can fail in ways that are invisible without external monitoring -- cold start timeouts, database connection limits, and API route errors that only affect specific paths.

  • Serverless cold starts -- Functions that haven't been invoked recently can take seconds to start, causing timeout errors for monitoring and users
  • Database connection limits -- Each serverless invocation may open a new database connection, quickly exhausting connection pools
  • Build/deploy failures -- A broken build on Vercel can leave an old version running, or worse, return build error pages
  • API route errors -- Server-side errors in API routes return 500s but don't crash the overall deployment

App Router (Next.js 13+)

// app/api/health/route.ts
import { NextResponse } from 'next/server';

export async function GET() {
  return NextResponse.json({
    status: 'healthy',
    timestamp: new Date().toISOString()
  });
}

// With dependency checks
// app/api/ready/route.ts
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';

export async function GET() {
  const checks: Record = {};
  let healthy = true;

  // Check database
  try {
    const start = Date.now();
    await db.execute('SELECT 1');
    checks.database = {
      status: 'ok',
      responseTimeMs: Date.now() - start
    };
  } catch (error) {
    healthy = false;
    checks.database = {
      status: 'error',
      message: error instanceof Error ? error.message : 'Unknown error'
    };
  }

  return NextResponse.json({
    status: healthy ? 'healthy' : 'unhealthy',
    timestamp: new Date().toISOString(),
    checks
  }, { status: healthy ? 200 : 503 });
}

Pages Router

// pages/api/health.ts
import type { NextApiRequest, NextApiResponse } from 'next';

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  res.status(200).json({
    status: 'healthy',
    timestamp: new Date().toISOString()
  });
}

// pages/api/ready.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { prisma } from '@/lib/prisma';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const checks: Record = {};
  let healthy = true;

  try {
    const start = Date.now();
    await prisma.$queryRaw`SELECT 1`;
    checks.database = {
      status: 'ok',
      responseTimeMs: Date.now() - start
    };
  } catch (error) {
    healthy = false;
    checks.database = { status: 'error' };
  }

  res.status(healthy ? 200 : 503).json({
    status: healthy ? 'healthy' : 'unhealthy',
    checks
  });
}

Edge Runtime Health Check

// app/api/health/route.ts
import { NextResponse } from 'next/server';

export const runtime = 'edge';

export async function GET() {
  return NextResponse.json({
    status: 'healthy',
    timestamp: new Date().toISOString(),
    runtime: 'edge'
  });
}

Check External APIs

// app/api/ready/route.ts
async function checkExternalAPI(url: string, name: string) {
  try {
    const start = Date.now();
    const response = await fetch(url, {
      method: 'HEAD',
      signal: AbortSignal.timeout(5000)
    });
    return {
      status: response.ok ? 'ok' : 'error',
      responseTimeMs: Date.now() - start,
      statusCode: response.status
    };
  } catch (error) {
    return {
      status: 'error',
      message: error instanceof Error ? error.message : 'Unknown error'
    };
  }
}

export async function GET() {
  const checks = await Promise.all([
    checkExternalAPI('https://api.stripe.com/v1', 'stripe'),
    checkExternalAPI('https://api.github.com', 'github')
  ]);

  return NextResponse.json({
    status: 'healthy',
    checks: {
      stripe: checks[0],
      github: checks[1]
    }
  });
}

Vercel Deployment Considerations

  • Cold starts — Serverless functions may be slow on first request
  • Function timeouts — Default 10s (hobby) or 60s (pro)
  • Edge vs Serverless — Edge functions are faster but have limitations
  • No persistent connections — Database connections are per-request

Best Practices

  • Use Edge runtime for simple checks — Faster response times globally
  • Add timeouts to external checks — AbortSignal.timeout() prevents hangs
  • Monitor serverless function invocations — Track cold start frequency
  • Include version info — Helps debug deployment issues

Common Next.js Production Issues

Cold Start Timeouts

Serverless functions on Vercel or AWS Lambda need to initialize on first invocation. Heavy dependencies (Prisma, large npm packages) increase cold start times. This can cause health checks to fail intermittently. Use Edge Runtime for simple health endpoints to avoid cold starts entirely.

Database Connection Exhaustion

Each serverless function invocation may create a new database connection. Under traffic spikes, this can exhaust your database's connection limit. Use a connection pooler like PgBouncer, Prisma Accelerate, or Neon's serverless driver. Monitor connection counts in your health check.

ISR Revalidation Failures

Incremental Static Regeneration can fail silently, serving stale content indefinitely. If your ISR pages depend on an API or database that goes down, pages stop revalidating but keep serving cached versions. External monitoring catches when your data source is unreachable.

Middleware Errors

Next.js middleware runs on every request and executes at the Edge. An error in middleware can break your entire application. Since middleware runs before API routes, a crash here means health checks also fail -- which is actually a useful signal that something is fundamentally broken.

Frequently Asked Questions

How do I add a health check to a Next.js App Router project?
Create a route handler at app/api/health/route.ts that exports a GET function returning NextResponse.json({ status: 'healthy' }). For readiness checks, add a separate route at app/api/ready/route.ts that verifies database and external API connectivity before returning 200 or 503.
Should I use Edge Runtime or Node.js Runtime for health checks?
Use Edge Runtime (export const runtime = 'edge') for simple liveness checks -- it has faster cold starts and lower latency globally. Use Node.js Runtime for readiness checks that need database connections or Node-specific APIs. Edge functions can't use most Node.js built-in modules or native database drivers.
How do I handle cold starts when monitoring on Vercel?
Serverless functions on Vercel experience cold starts after periods of inactivity, which can add 1-3 seconds to response time. Set your monitoring timeout to at least 10 seconds to avoid false positives. UptimeSignal's default timeout accommodates cold starts. For faster responses, use Edge Runtime or Vercel's cron feature to keep functions warm.
Can I monitor both Pages Router and App Router health endpoints?
Yes. Pages Router health checks go in pages/api/health.ts, while App Router checks go in app/api/health/route.ts. Both produce the same /api/health URL. If migrating from Pages to App Router, keep one implementation and remove the other. UptimeSignal doesn't care which router handles the request.
How often should I monitor my Next.js application?
For production Next.js apps, check every 1-5 minutes. UptimeSignal's free tier checks every 5 minutes, which catches most outages. Pro tier supports 1-minute intervals. Since Vercel's serverless functions are ephemeral, frequent checks also help detect platform-level issues that affect your deployment.

Monitor your Next.js app

Add your health endpoint to UptimeSignal and get alerted when it fails. Free tier includes 25 monitors.

Start monitoring free →

No credit card required. Commercial use allowed.

More Framework Guides