# Install on Next.js

Shows how much of your site's traffic is AI agents, and which ones. This package runs in your Next.js proxy (or middleware), and for each page request it sends a small event to your Punchline Analytics collector, after the response has gone out.

- **No cookies, no browser script, nothing added to your pages.**
- **Doesn't slow pages down.** The event is sent after the response; our code adds about 1 ms.
- **Never breaks your site.** If Punchline is misconfigured or unreachable, requests continue normally.

## Install

```bash
npm install @punchline-analytics/next
```

Set one environment variable. We send you the value when your site is set up:

```bash
PUNCHLINE_DSN=https://<site-id>:<secret>@<your-collector>/api/collect
```

**Next.js 16:** create `proxy.ts` at the project root, or in `src/` if you use one:

```ts
import { punchline } from "@punchline-analytics/next";

export const proxy = punchline();

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

**Next.js 15, or to run on the edge runtime:** use `middleware.ts` with `export const middleware = punchline();` and the same `config`.

Next.js reads `matcher` at build time, so it has to be written out in your file as shown.

## Already have a proxy or middleware?

Call the tracker inside it (your proxy can be `async`). To [serve AI agents markdown](https://www.punchlineanalytics.com/docs/edge-markdown) as well, answer with `serveMarkdown` first:

```ts
import { COUNTED_TIMING, createTracker } from "@punchline-analytics/next";
import { NextResponse, type NextFetchEvent, type NextRequest } from "next/server";

const tracker = createTracker();

export async function proxy(request: NextRequest, event: NextFetchEvent) {
  if (await tracker.isInternal(request)) return NextResponse.next();
  const markdown = await tracker.serveMarkdown(request, event);
  if (markdown) return markdown;
  const { overheadMs } = tracker.track(request, event);
  // …your existing logic…
  const response = NextResponse.next();
  // With the browser snippet installed too, this stops it counting the page load again.
  if (overheadMs !== null) response.headers.set("Server-Timing", COUNTED_TIMING);
  return response;
}
```

## What gets sent

For each page view (`GET`/`HEAD` for pages, plus `.txt`, `.md` and `.xml` files such as `robots.txt` and `llms.txt`), the package sends one event, server to server:

- The host, the path and `utm_*` values. Every other query parameter, and the `#fragment`, are removed before sending.
- The referrer's host only.
- The `Accept` header, cut to 200 characters.
- Whether the browser sent `Sec-GPC: 1`.
- The country code your host provides (`x-vercel-ip-country` on Vercel).
- Web Bot Auth signature headers, when an agent sends them.
- The visitor's IP address and user agent. The collector uses these in memory to classify the request and to compute a daily visitor hash, then discards them; they're never stored or logged.

Not tracked: images, scripts, styles, other assets, or Next.js link prefetches.

## Options

```ts
punchline({
  dsn: "...",            // default: process.env.PUNCHLINE_DSN
  timeoutMs: 3000,       // give up sending after this long
  serverTiming: true,    // add the overhead to Server-Timing: `punchline;dur=…` (default: PUNCHLINE_SERVER_TIMING=1)
  onError: (err) => {},  // configuration or network errors (default: ignored)
});
```

## Troubleshooting

- **No events arriving.** Check `PUNCHLINE_DSN` is set for the environment you deployed. Pass `onError: console.error` once to see why sends are failing.
- **Measuring the overhead.** Set `PUNCHLINE_SERVER_TIMING=1` and look at the `Server-Timing` response header.
