Rankealo

Next.js / Vercel

Add one proxy.ts file to your Next.js app. It runs before every page request, reports crawler visits to Rankealo in the background, and never changes what humans see.

Prerequisites#

  • A Next.js app (App Router or Pages Router) that you can deploy, on Vercel or self-hosted.
  • Your site token and website id from Bot traffic → Agent Analytics Setup in the app. The snippet there already has both filled in; the examples on this page use YOUR_SITE_TOKEN and YOUR_WEBSITE_ID. See the HTTP API reference for the full payload.

Steps#

  1. 1

    Create the file

    Create proxy.ts at the root of your project (next to app/ or pages/, or inside src/ if you use it). On Next.js 15 and older the file is middleware.ts and the exported function must be named middleware.
  2. 2

    Paste the snippet

    Use the snippet from Agent Analytics Setup (it has your token filled in), or the one below with your values. If you already have a proxy or middleware file, merge the crawler block into it.
  3. 3

    Deploy

    Deploy as usual. Nothing else needs configuring.
ts
import { NextResponse, type NextFetchEvent, type NextRequest } from "next/server";

const CRAWLER_UA = /bot|crawler|spider|chatgpt|gptbot|claude|perplexity|bing|google|applebot|bytespider|ccbot/i;
const SNAPSHOT_UA = /gptbot|chatgpt-user|oai-searchbot|claudebot|claude-user|claude-searchbot|perplexitybot|perplexity-user|google-extended/i;
const STATIC_ASSET = /\.(?:js|css|png|jpe?g|gif|webp|svg|ico|woff2?)$/i;

export async function proxy(request: NextRequest, event: NextFetchEvent) {
  if (request.method === "GET" || request.method === "HEAD") {
    const path = request.nextUrl.pathname;
    const ua = request.headers.get("user-agent") ?? "";
    if (!STATIC_ASSET.test(path) && CRAWLER_UA.test(ua)) {
      const href = request.nextUrl.origin + path;
      event.waitUntil(
        fetch("https://app.rankealo.ai/api/ai-visibility/crawler-hits/ingest", {
          method: "POST",
          headers: {
            "content-type": "application/json",
            authorization: "Bearer YOUR_SITE_TOKEN",
          },
          body: JSON.stringify({
            websiteId: "YOUR_WEBSITE_ID",
            domain: request.nextUrl.hostname,
            href,
            ai: { userAgent: ua, ip: request.headers.get("x-forwarded-for"), source: "server_middleware" },
          }),
        }).catch(() => {})
      );
      if (SNAPSHOT_UA.test(ua)) {
        const snap = await fetch("https://app.rankealo.ai/api/ai-visibility/bot-snapshot?websiteId=YOUR_WEBSITE_ID&path=" + encodeURIComponent(path), {
          headers: { authorization: "Bearer YOUR_SITE_TOKEN" },
        }).catch(() => null);
        if (snap && snap.ok) {
          const html = await snap.text();
          if (html.length > 200) {
            return new NextResponse(html, { status: 200, headers: { "content-type": "text/html; charset=utf-8" } });
          }
        }
      }
    }
  }
  return NextResponse.next();
}

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

Connect GitHub instead

If your repo is on GitHub, you can connect it from Agent Analytics Setup and we can add this file for you in a pull request.

What is sent#

  • URLOrigin and path. The query string is never sent.
  • User agentUsed to identify the crawler.
  • IPFrom x-forwarded-for; hashed on arrival, never stored raw.
  • Website idTies the hit to your site and token.

Only requests whose user agent looks like a crawler are reported, and static assets are skipped. Requests are sent with event.waitUntil, so the page response never waits for Rankealo.

Bot rendering (optional)#

For AI crawlers (GPTBot, ChatGPT-User, ClaudeBot, PerplexityBot and similar), the snippet also asks Rankealo for a stored HTML copy of the page. This only returns something when bot rendering is turned on for your site; otherwise the request continues to your app unchanged.

Limitations#

  • The matcher skips /api, _next/static, _next/image, and favicon.ico. Crawls of those paths are not counted.
  • Pages served straight from a CDN cache in front of Next.js without running the proxy are not seen. On Vercel, the proxy runs on every matched request.
  • No status code is sent, because the proxy runs before the page renders.

Confirm data is flowing#

  1. 1
    In Rankealo, open Bot traffic → Agent Analytics Setup.
  2. 2
    Press Check now in step 3. Once a crawler has visited, it shows Receiving data with the time of the last hit.
  3. 3
    Open the Bot traffic tab to see hits by crawler and by page.

The first hit depends on when a crawler next visits, which can take up to 24 hours. To test right away, request a page with a crawler user agent, for example curl -A "GPTBot/1.2" https://www.your-site.com/, then press Check now. A test like this is recorded as a claimed identity, like any hit without CDN verification.

Troubleshooting#

Nothing shows up after deploying+
Check the file is at the project root (or in src/) and is named correctly for your Next.js version: proxy.ts on 16+, middleware.ts with a middleware export on 15 and older.
Check now says No data received+
Request a page with curl -A "GPTBot/1.2" and check again. If it is still empty, confirm the token and website id match the ones in Agent Analytics Setup.
Hits are skipped+
The hostname in the URL must belong to the website you set up in Rankealo. Preview deployments on *.vercel.app are ignored on purpose.

Still stuck? Contact support

Reading is step one. Measuring your AI visibility is step two.

Rankealo tracks how often your brand is mentioned and cited across the major AI engines, then helps you publish the pages that close the gaps.