Docs/Next.js

Next.js

Add monitoring to your Next.js app with App Router. Set up a health endpoint, push heartbeats with Vercel Cron, and embed a status badge.

Prerequisites

  • Next.js 13+ with App Router
  • Your app deployed and accessible (e.g., on Vercel)
  • Submit your service on BlueMonitor (or it auto-registers via the badge)
1

Health endpoint

Create a simple health endpoint that BlueMonitor will ping every 5 minutes. We check the response status and latency.

app/api/health/route.ts

export async function GET() {
  return Response.json({ status: "ok" });
}

That's it. BlueMonitor will call GET /api/health and mark your service as operational if it responds with 200 under 3 seconds.

2

Heartbeat push (optional)

For detailed dependency monitoring, push heartbeats from your service to BlueMonitor. This checks your database, cache, and external APIs internally and reports the results. Requires a BlueMonitor API key.

app/api/cron/heartbeat/route.ts

import { db } from "@/lib/db"; // adjust to your setup

async function checkDependency(name: string, fn: () => Promise<void>) {
  const start = Date.now();
  try {
    await fn();
    return { status: "ok" as const, latency: Date.now() - start };
  } catch (err) {
    return {
      status: "error" as const,
      latency: Date.now() - start,
      message: err instanceof Error ? err.message : "Unknown error",
    };
  }
}

export async function GET() {
  const checks = {
    database: await checkDependency("database", async () => {
      await db`SELECT 1`;
    }),
    // redis: await checkDependency("redis", async () => { await redis.ping(); }),
    // stripe: await checkDependency("stripe", async () => { await stripe.balance.retrieve(); }),
  };

  const hasError = Object.values(checks).some((c) => c.status === "error");
  const status = hasError ? "error" : "ok";

  await fetch("https://www.bluemonitor.org/api/v1/heartbeat", {
    method: "POST",
    headers: {
      Authorization: "Bearer bm_your_api_key",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      domain: "yourapp.com",
      status,
      timestamp: new Date().toISOString(),
      checks,
    }),
  });

  return Response.json({ ok: true });
}

vercel.json

{
  "crons": [
    {
      "path": "/api/cron/heartbeat",
      "schedule": "*/5 * * * *"
    }
  ]
}

Vercel Cron runs this every 5 minutes. If BlueMonitor stops receiving heartbeats for 10 minutes, your service is marked as down.

3

Status badge

Add a live status badge to your app. Replace your-domain-com with your domain (dots become dashes).

React component

function StatusBadge({ slug }: { slug: string }) {
  return (
    <a
      href={`https://www.bluemonitor.org/status/${slug}`}
      target="_blank"
      rel="noopener"
    >
      <img
        src={`https://www.bluemonitor.org/api/badge/${slug}`}
        alt="Status on BlueMonitor"
        height={36}
      />
    </a>
  );
}

// Usage: <StatusBadge slug="your-domain-com" />

Markdown (README.md)

[![Status](https://www.bluemonitor.org/api/badge/your-domain-com)](https://www.bluemonitor.org/status/your-domain-com)

Environment variables

If using heartbeat push, add your API key to .env.local:

BLUEMONITOR_API_KEY=bm_your_api_key

Then reference it in your heartbeat route: process.env.BLUEMONITOR_API_KEY

4

Bot tracking

PRO

Track which search engines, AI crawlers, and social bots visit your app. Add bot detection to your proxy/middleware to report visits to BlueMonitor. Requires a Pro plan.

Next.js 16+: middleware.ts was renamed to proxy.ts and the exported function from middleware to proxy. For Next.js 13–15, use middleware.ts with the same code but export function middleware instead.

Migrate with: npx @next/codemod@canary middleware-to-proxy .

proxy.ts (Next.js 16+) or middleware.ts (Next.js 13–15)

import { NextRequest, NextResponse } from "next/server";

const BOT_PATTERNS = [
  { pattern: /Googlebot/i, name: "googlebot", category: "search_engine" },
  { pattern: /bingbot/i, name: "bingbot", category: "search_engine" },
  { pattern: /YandexBot/i, name: "yandexbot", category: "search_engine" },
  { pattern: /DuckDuckBot/i, name: "duckduckbot", category: "search_engine" },
  { pattern: /GPTBot/i, name: "gptbot", category: "ai_crawler" },
  { pattern: /ChatGPT-User/i, name: "chatgpt-user", category: "ai_crawler" },
  { pattern: /ClaudeBot/i, name: "claudebot", category: "ai_crawler" },
  { pattern: /anthropic-ai/i, name: "anthropic-ai", category: "ai_crawler" },
  { pattern: /PerplexityBot/i, name: "perplexitybot", category: "ai_crawler" },
  { pattern: /Bytespider/i, name: "bytespider", category: "ai_crawler" },
  { pattern: /CCBot/i, name: "ccbot", category: "ai_crawler" },
  { pattern: /Meta-ExternalAgent/i, name: "meta-externalagent", category: "ai_crawler" },
  { pattern: /Google-Extended/i, name: "google-extended", category: "ai_crawler" },
  { pattern: /Twitterbot/i, name: "twitterbot", category: "social" },
  { pattern: /facebookexternalhit/i, name: "facebookbot", category: "social" },
  { pattern: /LinkedInBot/i, name: "linkedinbot", category: "social" },
  { pattern: /AhrefsBot/i, name: "ahrefsbot", category: "seo" },
  { pattern: /SemrushBot/i, name: "semrushbot", category: "seo" },
  { pattern: /UptimeRobot/i, name: "uptimerobot", category: "monitoring" },
];

function identifyBot(ua: string) {
  for (const bot of BOT_PATTERNS) {
    if (bot.pattern.test(ua)) return { name: bot.name, category: bot.category };
  }
  return null;
}

// Next.js 16+: export as "proxy". Next.js 13–15: rename to "middleware".
export async function proxy(request: NextRequest) {
  const response = NextResponse.next();
  const ua = request.headers.get("user-agent") || "";
  const bot = identifyBot(ua);
  if (bot) {
    // waitUntil keeps the fetch alive without blocking the response
    response.waitUntil(
      fetch("https://www.bluemonitor.org/api/v1/bot-visits", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.BLUEMONITOR_API_KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          domain: "yourapp.com",
          visits: [{
            bot_name: bot.name,
            bot_category: bot.category,
            path: request.nextUrl.pathname,
            user_agent: ua,
          }],
        }),
      }).catch(() => {})
    );
  }
  return response;
}

export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};

response.waitUntil() keeps the fetch alive without blocking the response — your users get zero added latency. If you already have a proxy.ts (or middleware.ts), add the bot detection to your existing function. View results in your dashboard under Bot Tracking.

Using an AI coding tool?

Paste this URL in Claude, Cursor, or Copilot and it will set up monitoring for your Next.js app automatically.

https://www.bluemonitor.org/llm-nextjs.txt

Next steps