Vercel / Next.js Integration

Drop BotWatcher into your Next.js middleware — under 5 minutes.

Zero latency

Fire-and-forget fetch — never slows page loads

Server-side only

Detection logic never reaches the browser

Safe to merge

Works alongside your existing middleware

Before you start — grab these from your dashboard Setup tab:

BOTWATCHER_API_KEY

Your API key

Store as Secret in Vercel

BOTWATCHER_DOMAIN

yourdomain.com

Plain variable

Choose your setup

FRESH INSTALL

No existing middleware

1

Add environment variables

Add to your .env.local (for local dev) and to Vercel → Project → Settings → Environment Variables (for production). Mark BOTWATCHER_API_KEY as a Secret.

BOTWATCHER_API_KEY=your-api-key-here
BOTWATCHER_DOMAIN=yourdomain.com
# Optional — defaults to https://api.botwatcher.pro/edge
# BOTWATCHER_WEBHOOK_URL=https://api.botwatcher.pro/edge
.env
2

Create the proxy/middleware file

Next.js 16+ — create src/proxy.ts and use export function proxy.
Next.js 13–15 — create src/middleware.ts and rename the export to middleware (comment in the snippet tells you where).

// Next.js 16+  →  save as: src/proxy.ts  (or proxy.ts at project root)
// Next.js 13–15 →  save as: src/middleware.ts
import { NextResponse } from "next/server";
import type { NextFetchEvent, NextRequest } from "next/server";

export function proxy(req: NextRequest, event: NextFetchEvent) {
  const apiKey = process.env.BOTWATCHER_API_KEY;
  const domain = process.env.BOTWATCHER_DOMAIN;

  if (apiKey && domain) {
    // event.waitUntil keeps the function alive until the fetch completes
    // without blocking the response to the visitor.
    event.waitUntil(
      fetch(process.env.BOTWATCHER_WEBHOOK_URL ?? "https://api.botwatcher.pro/edge", {
        method: "POST",
        headers: { "Content-Type": "application/json", "x-api-key": apiKey },
        body: JSON.stringify({
          site:    domain,
          path:    req.nextUrl.pathname,
          method:  req.method,
          ua:      req.headers.get("user-agent") ?? "",
          ip:      req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? "",
          country: req.headers.get("x-vercel-ip-country") ?? "",
          city:    req.headers.get("x-vercel-ip-city") ?? "",
          region:  req.headers.get("x-vercel-ip-country-region") ?? "",
          referer: req.headers.get("referer") ?? "",
        }),
      }).catch(() => {})
    );
  }

  return NextResponse.next();
}

// Next.js 13–15: rename the export above to `middleware` instead of `proxy`

export const config = {
  matcher: [
    /*
     * Match all paths except:
     * - _next/static  (static files)
     * - _next/image   (image optimisation)
     * - favicon.ico
     */
    "/((?!_next/static|_next/image|favicon.ico).*)",
  ],
};
ts
3

Deploy to Vercel

Push to your main branch. Vercel will pick up the new middleware automatically — no config changes needed.

$git add src/middleware.ts
$git commit -m "Add BotWatcher middleware"
$git push
4

Verify it's working

Test the API endpoint directly — if this returns {"ok":true,"logged":true} your API key is valid and hits will appear in your dashboard.

curl -X POST https://api.botwatcher.pro/edge \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{"site":"yourdomain.com","path":"/test","method":"GET","ua":"GPTBot/1.0","ip":"1.2.3.4","country":"US","city":"","region":"","referer":""}'

# Expected response: {"ok":true,"logged":true}
bash

Then visit any page on your site with a bot user-agent (or just wait — real bots will show up within hours). Check the Recent Hits tab on your dashboard.

already have middleware?
EXISTING MIDDLEWARE

Safe merge

This will not break your existing middleware.

BotWatcher calls fetch() in a fire-and-forget pattern — it doesn't await a response, never throws, and always calls NextResponse.next() unless your existing logic returns a redirect/rewrite first.

1

Add environment variables

Same as the fresh install — add to .env.local and Vercel dashboard.

BOTWATCHER_API_KEY=your-api-key-here
BOTWATCHER_DOMAIN=yourdomain.com
# Optional — defaults to https://api.botwatcher.pro/edge
# BOTWATCHER_WEBHOOK_URL=https://api.botwatcher.pro/edge
.env
2

Open your existing middleware/proxy file

Your file is either proxy.ts (Next.js 16+) or middleware.ts (Next.js 13–15). You're going to add the BotWatcher helper to it — not replace your existing logic.

3

Add the BotWatcher helper and call it first

The template below shows the pattern. Replace the yourExistingMiddleware placeholder with your actual logic (or leave it as a separate function/import).

// Next.js 16+  →  save as: src/proxy.ts  (or proxy.ts at project root)
// Next.js 13–15 →  save as: src/middleware.ts
import { NextResponse } from "next/server";
import type { NextFetchEvent, NextRequest } from "next/server";

// ─── BotWatcher reporting helper ─────────────────────────────────────────────
function reportToBotWatcher(req: NextRequest, event: NextFetchEvent) {
  const apiKey = process.env.BOTWATCHER_API_KEY;
  const domain = process.env.BOTWATCHER_DOMAIN;
  if (!apiKey || !domain) return;

  event.waitUntil(
    fetch(process.env.BOTWATCHER_WEBHOOK_URL ?? "https://api.botwatcher.pro/edge", {
      method: "POST",
      headers: { "Content-Type": "application/json", "x-api-key": apiKey },
      body: JSON.stringify({
        site:    domain,
        path:    req.nextUrl.pathname,
        method:  req.method,
        ua:      req.headers.get("user-agent") ?? "",
        ip:      req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? "",
        country: req.headers.get("x-vercel-ip-country") ?? "",
        city:    req.headers.get("x-vercel-ip-city") ?? "",
        region:  req.headers.get("x-vercel-ip-country-region") ?? "",
        referer: req.headers.get("referer") ?? "",
      }),
    }).catch(() => {})
  );
}
// ─────────────────────────────────────────────────────────────────────────────

// ─── Paste your existing middleware/proxy logic below ────────────────────────
function yourExistingLogic(req: NextRequest): NextResponse | null {
  // e.g. auth checks, redirects, i18n, A/B flags…
  // return NextResponse.redirect(…) or NextResponse.rewrite(…)
  // return null to fall through to NextResponse.next()
  return null;
}
// ─────────────────────────────────────────────────────────────────────────────

export function proxy(req: NextRequest, event: NextFetchEvent) {
  // 1. Always report to BotWatcher — fire-and-forget, never blocks
  reportToBotWatcher(req, event);

  // 2. Run your existing logic — honour any redirect/rewrite it returns
  const existing = yourExistingLogic(req);
  if (existing) return existing;

  // 3. Default — continue to the page
  return NextResponse.next();
}

// Next.js 13–15: rename the export above to `middleware` instead of `proxy`

// Keep your existing matcher, or use this recommended one:
export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};

// Keep your existing matcher or use the BotWatcher recommended one below
export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};
ts

Key rules when merging:

  • Pass both req and event to reportToBotWatcher — the event.waitUntil() is what guarantees the fetch completes after the response is sent.
  • Your existing redirects and rewrites are fully preserved — BotWatcher never overrides them.
  • Next.js 16+: rename your function export from middleware to proxy and your file to proxy.ts. The snippet comment reminds you.
  • Do not use bare fetch().catch() without event.waitUntil() — the runtime may terminate before the fetch completes.
4

Deploy and verify

Push your changes. Then run the same curl test to confirm your API key works:

curl -X POST https://api.botwatcher.pro/edge \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{"site":"yourdomain.com","path":"/test","method":"GET","ua":"GPTBot/1.0","ip":"1.2.3.4","country":"US","city":"","region":"","referer":""}'

# Expected response: {"ok":true,"logged":true}
bash
Troubleshooting

No hits appearing in the dashboard

Check that BOTWATCHER_API_KEY and BOTWATCHER_DOMAIN are set in Vercel's environment variables (not just .env.local). Re-deploy after adding them. Also check that BOTWATCHER_DOMAIN matches exactly what you signed up with — no http://, no trailing slash.

Proxy/middleware not running at all

Next.js 16+: the file must be proxy.ts (not middleware.ts) and export function proxy. Next.js 13–15: the file must be middleware.ts and export function middleware. Both must be at the root of your project or inside src/ — not inside app/ or pages/.

Getting 401 Unauthorized from the API

Your API key is wrong or not being read. Log process.env.BOTWATCHER_API_KEY in the middleware temporarily to verify it's being injected. Remember: env vars prefixed with NEXT_PUBLIC_ are exposed to the browser — never use that prefix for your API key.

My existing redirects stopped working

Make sure your existing middleware returns its response and that BotWatcher's reportToBotWatcher() call is not inside an if-else that blocks it. The fire-and-forget fetch should always be called regardless of what your logic does afterward.

Set up? Head to your dashboard to watch bot traffic arrive.