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 = [
  // Search engines
  { 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: /Baiduspider/i, name: "baiduspider", category: "search_engine" },
  { pattern: /DuckDuckBot/i, name: "duckduckbot", category: "search_engine" },
  { pattern: /Applebot/i, name: "applebot", category: "search_engine" },
  { pattern: /PetalBot/i, name: "petalbot", category: "search_engine" },
  { pattern: /Sogou/i, name: "sogoubot", category: "search_engine" },
  { pattern: /NaverBot/i, name: "naverbot", category: "search_engine" },
  { pattern: /Seznambot/i, name: "seznambot", category: "search_engine" },
  // AI crawlers
  { pattern: /GPTBot/i, name: "gptbot", category: "ai_crawler" },
  { pattern: /ChatGPT-User/i, name: "chatgpt-user", category: "ai_crawler" },
  { pattern: /OAI-SearchBot/i, name: "oai-searchbot", 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: /Amazonbot/i, name: "amazonbot", category: "ai_crawler" },
  { pattern: /cohere-ai/i, name: "cohere-ai", category: "ai_crawler" },
  { pattern: /DeepSeekBot/i, name: "deepseekbot", category: "ai_crawler" },
  { pattern: /YouBot/i, name: "youbot", category: "ai_crawler" },
  { pattern: /AI2Bot/i, name: "ai2bot", category: "ai_crawler" },
  { pattern: /Timpibot/i, name: "timpibot", category: "ai_crawler" },
  // Social
  { pattern: /Twitterbot/i, name: "twitterbot", category: "social" },
  { pattern: /facebookexternalhit/i, name: "facebookbot", category: "social" },
  { pattern: /LinkedInBot/i, name: "linkedinbot", category: "social" },
  { pattern: /Slackbot/i, name: "slackbot", category: "social" },
  { pattern: /Discordbot/i, name: "discordbot", category: "social" },
  { pattern: /TelegramBot/i, name: "telegrambot", category: "social" },
  { pattern: /WhatsApp/i, name: "whatsapp", category: "social" },
  { pattern: /Pinterestbot/i, name: "pinterestbot", category: "social" },
  // SEO
  { pattern: /AhrefsBot/i, name: "ahrefsbot", category: "seo" },
  { pattern: /SemrushBot/i, name: "semrushbot", category: "seo" },
  { pattern: /DotBot/i, name: "dotbot", category: "seo" },
  { pattern: /MJ12bot/i, name: "mj12bot", category: "seo" },
  { pattern: /DataForSeoBot/i, name: "dataforseobot", category: "seo" },
  { pattern: /Screaming Frog/i, name: "screamingfrog", category: "seo" },
  { pattern: /BLEXBot/i, name: "blexbot", category: "seo" },
  // Monitoring
  { pattern: /UptimeRobot/i, name: "uptimerobot", category: "monitoring" },
  { pattern: /Pingdom/i, name: "pingdom", category: "monitoring" },
  { pattern: /NewRelicPinger/i, name: "newrelicpinger", category: "monitoring" },
  { pattern: /Datadog/i, name: "datadoghq", category: "monitoring" },
  { pattern: /Site24x7/i, name: "site24x7", 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.

5

AI Attribution

PRO

Track real users arriving at your site from AI sources like ChatGPT, Perplexity, and Claude. Install @bluemonitor/middleware and add one line to your proxy.ts (or middleware.ts). Requires a Pro plan.

Install

npm install @bluemonitor/middleware

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

import { bluemonitorMiddleware } from "@bluemonitor/middleware";

export default bluemonitorMiddleware({
  apiKey: process.env.BLUEMONITOR_API_KEY!,
  domain: "yourapp.com",
});

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

The middleware detects Referer headers from chatgpt.com, perplexity.ai, claude.ai, and you.com and sends attribution data to BlueMonitor without blocking your users' requests. If you already have a proxy.ts with bot tracking, you can chain both:

import { bluemonitorMiddleware } from "@bluemonitor/middleware";
import { NextRequest, NextResponse } from "next/server";

const attribution = bluemonitorMiddleware({
  apiKey: process.env.BLUEMONITOR_API_KEY!,
  domain: "yourapp.com",
});

export async function proxy(request: NextRequest) {
  // Bot tracking (existing)
  const ua = request.headers.get("user-agent") || "";
  // ... your bot detection logic ...

  // AI attribution (new)
  await attribution(request);

  return NextResponse.next();
}

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

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