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

> Documentation Index
> Fetch the complete documentation index at: /llms.txt
> Use this file to discover all available pages before exploring further.

# Vercel Integration

Both options power [AI Impressions](/docs/impressions/); to also track [Clicks](/docs/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:

```text
https://api.getairefs.com/v1/logs/vercel
```

4.  Format: **JSON** or **NDJSON** (both work).
5.  Add a custom header: `Authorization: Bearer YOUR_ACCESS_TOKEN`.
6.  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.

```ts
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](mailto:support@getairefs.com).

Source: /docs/integrations/vercel/
