
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_TOKENandYOUR_WEBSITE_ID. See the HTTP API reference for the full payload.
Steps#
- 1
Create the file
Createproxy.tsat the root of your project (next toapp/orpages/, or insidesrc/if you use it). On Next.js 15 and older the file ismiddleware.tsand the exported function must be namedmiddleware. - 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
Deploy
Deploy as usual. Nothing else needs configuring.
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, andfavicon.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#
- 1In Rankealo, open Bot traffic → Agent Analytics Setup.
- 2Press Check now in step 3. Once a crawler has visited, it shows Receiving data with the time of the last hit.
- 3Open 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+
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+
Hits are skipped+
Still stuck? Contact support
