Skip to content
Vercel Integration

Vercel Integration

Track AI crawler visits on Vercel — a zero-code Log Drain on Pro and Enterprise, or a small middleware on any plan including Hobby.

Updated
Ask AI
View as Markdown

Both options power AI Impressions; to also track Clicks, add the client-side tracking script from Site Settings → Tracking Script. Send all traffic — Airefs classifies bots versus humans and drops assets server-side. Error responses are also dropped for the Log Drain; the middleware cannot observe response status, so it does not get this filter.

Your access token is in your Airefs account under Site Settings → Access Token.

Option A: Log Drain (Pro or Enterprise)

Recommended when your plan allows it: no code, and it also captures static and CDN-cached requests that middleware never sees.

  1. In the Vercel dashboard, open Team Settings → Drains and click Add Drain.
  2. Data type: Logs. Sources: Functions, Edge Functions, Static Files, Rewrites, Redirects. Environment: Production.
  3. Delivery: Custom Endpoint with this URL:
https://api.getairefs.com/v1/logs/vercel
  1. Format: JSON or NDJSON (both work).
  2. Add a custom header: Authorization: Bearer YOUR_ACCESS_TOKEN.
  3. Create the drain. Vercel tests the endpoint at creation; it succeeds when the URL and header are correct.

Option B: Middleware (any plan)

Install @vercel/functions and drop this middleware.ts into the root of your Next.js project (merge into your existing middleware if you have one). Then add AIREFS_ACCESS_TOKEN to the project’s Production environment variables and deploy.

import { waitUntil } from "@vercel/functions";
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";

const AIREFS_LOG_ENDPOINT = "https://api.getairefs.com/v1/logs/http";

async function forwardLogRecord(request: NextRequest): Promise<void> {
  const accessToken = process.env.AIREFS_ACCESS_TOKEN;
  if (!accessToken) {
    return;
  }

  const eventId = request.headers.get("x-vercel-id");
  if (!eventId) {
    return;
  }

  const forwardedFor = request.headers.get("x-forwarded-for");
  const record = {
    event_id: eventId,
    timestamp: new Date().toISOString(),
    request_method: request.method,
    request_path: request.nextUrl.pathname,
    query_string: request.nextUrl.search.slice(1) || undefined,
    hostname: request.headers.get("host") ?? undefined,
    client_ip: forwardedFor?.split(",")[0]?.trim() || undefined,
    user_agent: request.headers.get("user-agent") ?? undefined,
    referrer: request.headers.get("referer") ?? undefined,
    country_code: request.headers.get("x-vercel-ip-country") ?? undefined,
    event_client: "vercel-middleware@1.0.0",
  };

  await fetch(AIREFS_LOG_ENDPOINT, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": accessToken,
    },
    body: JSON.stringify(record),
  });
}

export default function middleware(request: NextRequest) {
  waitUntil(forwardLogRecord(request).catch(() => {}));
  return NextResponse.next();
}

// Excludes only known Next.js internals; extension-based asset filtering
// happens server-side so pages with dots in the path (e.g. /docs/v1.2/notes)
// are still tracked.
export const config = {
  matcher: ["/((?!_next/static|_next/image|_next/data|favicon.ico).*)"],
};

The POST runs after the response is returned, so it adds no latency. One limitation: middleware cannot observe the response status, so error pages are recorded as pageviews too — the Log Drain does not have this limitation. On Next.js 15.1+ you can use after() from next/server instead of waitUntil.

Verification

Visit a few pages on your production deployment. Events appear in your Airefs dashboard within a few minutes.

Outside dry-run mode, a successful response means Airefs stored every accepted record and reports why the rest were dropped. For a one-off test before going live, POST a sample record with ?dry_run=1 appended to the endpoint URL: Airefs returns a per-record verdict, including the bot classification it detected, without storing anything.

Drain creation failing the endpoint test? Check the Authorization header value starts with Bearer and the token is correct. Middleware deployed but no events? Confirm AIREFS_ACCESS_TOKEN is set for Production. Need help? Email support@getairefs.com.