Skip to content

AI visit tracking

Geoffy can show you which AI systems read your pages (your real product pages, not only the files Geoffy serves) and which shoppers arrive from an AI assistant. It takes one small middleware, and it is entirely optional: nothing else in these guides depends on it.

It needs @geoffy/headless 0.3.1 or later.

It uses the two variables you already have, GEOFFY_SITE_KEY and GEOFFY_REVALIDATE_SECRET. Reports are signed with that secret, and without it nothing is sent. If you skipped the revalidate route, copy the secret from your site’s settings in Geoffy anyway.

proxy.ts
import { createGeoffyAiVisitMiddleware } from "@geoffy/headless/next-middleware";
export const proxy = createGeoffyAiVisitMiddleware({ siteKey: process.env.GEOFFY_SITE_KEY! });

That is proxy.ts on Next 16. On earlier versions the file is middleware.ts and the export is export const middleware.

It returns nothing, which Next reads as “carry on”, so your pages are served exactly as before.

Call it from inside yours, and keep returning your own response:

proxy.ts
import type { NextFetchEvent, NextRequest } from "next/server";
import { createGeoffyAiVisitMiddleware } from "@geoffy/headless/next-middleware";
const trackAiVisits = createGeoffyAiVisitMiddleware({ siteKey: process.env.GEOFFY_SITE_KEY! });
export function proxy(request: NextRequest, event: NextFetchEvent) {
trackAiVisits(request, event);
return yourExistingLogic(request);
}

The report is handed to event.waitUntil, so it finishes after the response. To use Next’s after instead, pass it: createGeoffyAiVisitMiddleware({ siteKey, defer: after }).

What it reports

It reports a request when either of these is true:

  • The user agent names a known AI agent: GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, PerplexityBot, Google-Extended, Amazonbot, CCBot, bingbot and others.
  • The visitor arrived from an AI assistant. The referer is, or utm_source names, chatgpt.com, chat.openai.com, perplexity.ai, gemini.google.com, copilot.microsoft.com, claude.ai or grok.com.

Only GET and HEAD requests count, and prefetches are ignored. Every other request, which is every ordinary page view, makes no network call at all. The decision is made on your server, so your shoppers' visits never leave it.

What it sends, and what it never does

For each reported request it sends the time, the page's URL without its query string, and the assistant's host or utm_source.

  • For a request that claims to be an AI crawler, it also sends the user agent and, only where your platform vouches for one, the visitor's address. That is what lets Geoffy check the claim against the vendor's published address ranges.
  • For a shopper arriving from an assistant, it sends neither: no user agent and no address.
  • It reads no cookies and sets none, so it needs no consent banner.

A crawler sweep

Anyone can send a crawler's user agent, so each server process makes at most 60 calls a minute to Geoffy. The limit is on calls, not on visits. An AI crawler can read hundreds of your pages in a minute, so a visit that arrives after the budget is spent waits for the next call instead of being thrown away, and one call carries up to 50 visits. Your server makes the same small number of calls either way.

Crawler visits and assistant referrals have separate budgets and separate queues, so a flood of claimed crawlers can neither spend the calls nor fill the queue your shoppers depend on. Each queue holds up to 500 visits. In a burst big enough to fill it, or when a visit has waited more than five minutes, the oldest are given up on. Their count travels with the next report, so the figures Geoffy shows you say they are a minimum instead of presenting a short number as a total.

New AI agents

The list of AI agents ships with the package and is topped up from Geoffy once a day, so a new crawler is recognised without an upgrade. If the update cannot be read, the built-in list applies.

Where your site runsDefault
Vercel, with visitors reaching Vercel directlyThe address Vercel sets for the request. Nothing to configure
Vercel behind another CDN or proxyVercel sees the CDN’s address, so crawler visits cannot be confirmed. Pass clientIp with the address your CDN vouches for
Anywhere elseNone. Visits are reported without an address

The default never reads X-Forwarded-For. Its first entry is whatever the client chose to send, so trusting it would let anyone claim to be a crawler from a vendor’s address range.

If your platform gives you an address you can trust, pass it:

createGeoffyAiVisitMiddleware({
siteKey: process.env.GEOFFY_SITE_KEY!,
// e.g. an address your own load balancer wrote, which clients cannot set
clientIp: (request) => request.headers.get("x-your-trusted-client-ip"),
});
OptionDefault
siteKeynoneRequired. Without it nothing is sent
secretGEOFFY_REVALIDATE_SECRETSigns each report. Without one nothing is sent
originas everywhere else in this package
clientIpsee above(request) => string | null | undefined
exclude/_next/ and static filesExtra paths never reported. A string is a path prefix, a RegExp is tested against the path. Added to the defaults, never instead of them
sample1Report this fraction of matching requests, 0 to 1
timeoutMs3000Give up on a report after this long. It never delays your response
deferevent.waitUntilNext’s after, if you prefer it

The namespace route already tells Geoffy which AI agent asked for a file, by the agent’s name (GPTBot). A browser’s user agent is never passed on. So even without the middleware, Geoffy sees the agents that read your product text versions and your guides, as often as your cache fetches them from Geoffy.

The site-wide text routes can do the same, but only when you ask. It means reading the request headers, which makes Next render the route on every request instead of serving it from its static or cached output:

app/llms.txt/route.ts
import { createGeoffyTextRoute } from "@geoffy/headless/next";
export const GET = createGeoffyTextRoute(
{ siteKey: process.env.GEOFFY_SITE_KEY! },
"llms.txt",
{ forwardCrawler: true },
);

What only the middleware can show is who reads your own product pages, and who arrives from an assistant.