Same-Origin Collector for Next.js / Vercel
Add a Route Handler to your Next.js app to get the same 400-day, Safari-proof visitor identity as the Cloudflare Worker template
If your site runs on Next.js (App Router) — on Vercel or anywhere else — you can add a small Route Handler instead of deploying a Cloudflare Worker to get the same result: visitors stay identified on Safari and iPhone for up to 400 days instead of 7.
Read this first
This page assumes you already read Same-Origin Collector — it explains why this exists (Safari's ITP cap, and why a custom tracking domain CNAME does not fix it). This page is the Next.js-specific "how".
Why a rewrite is not enough
A plain next.config.ts rewrites() entry forwards the request and passes cookies through, but it cannot add a header. Atribu only trusts the forwarded visitor IP and issues the atb_vid cookie when the request carries your Profile's proxy secret (x-atribu-proxy-secret) — a value that must never reach the browser. A rewrite has no way to attach it.
So the Next.js recipe is a real Route Handler: a few lines of server code that add the secret, forward the visitor's real IP, and relay Atribu's response — including its Set-Cookie — unchanged.
Setup
Save your addresses in Atribu
Go to Settings > Tracking > Domains > Same-origin collector and enter:
- Your site —
https://yourstore.com - Collector path on your site —
https://yourstore.com/atb
Click Save. Atribu shows a proxy secret once. Copy it now.
Install the package
npm install @atribu/tracker@atribu/tracker/next is a separate entry point inside the same package — a tiny, dependency-free proxy helper. It does not pull in the browser tracker runtime.
Add the route
Create a catch-all Route Handler at the path you chose (/atb by default):
import { createAtribuProxy } from "@atribu/tracker/next";
export const { GET, POST } = createAtribuProxy({
secret: process.env.ATRIBU_PROXY_SECRET!,
path: "/atb",
});createAtribuProxy serves the tracker script at <path>/atribu-tracker.js and forwards every <path>/api/tracking/* call to Atribu with the header the trust check requires.
Set the secret
Add the secret from step 1 as an environment variable in your Vercel project (Settings > Environment Variables), or your .env.local for local testing:
ATRIBU_PROXY_SECRET=atbps_...Server-only
ATRIBU_PROXY_SECRET must not be prefixed NEXT_PUBLIC_ — it is read only inside the Route Handler, which runs on the server. Prefixing it would ship it to every visitor's browser.
Deploy, then check the installation
Deploy your app. Back in Atribu, click Check installation. It passes when:
- the path answers through your route and reaches Atribu with the right secret, and
- it answers from the same network as your site (Safari's condition for keeping the cookie) — automatic here, since the route lives on your site's own domain.
Replace your tracking code
Copy the Tracking code for this mode shown in Atribu and replace your current Atribu snippet with it. It loads the tracker from yourstore.com/atb/atribu-tracker.js.
Visitors already on your site keep their history
The first time a returning visitor reaches the new collector, their current Atribu ID becomes the long-lived one. Nobody is reset.
Do not use the Edge runtime
Do not add export const runtime = "edge"; to the route. The proxy needs the visitor's real IP, which it reads from x-forwarded-for / x-real-ip the way Vercel's Node.js functions receive them — the default runtime for a Route Handler, and the one this recipe is written for. The Edge runtime sits behind a different boundary and is not what this recipe was measured against.
Sharing the cookie across subdomains
By default the cookie belongs to the exact hostname the visitor is on. If your store spans www.yourstore.com and shop.yourstore.com, pass cookieDomain:
export const { GET, POST } = createAtribuProxy({
secret: process.env.ATRIBU_PROXY_SECRET!,
path: "/atb",
cookieDomain: "yourstore.com",
});What the proxy sends, and what it does not
Same rules as the Cloudflare Worker template — this recipe implements the identical contract:
- It forwards only what the tracker needs: the event, the browser's user agent and language, the page address, and the visitor's IP address.
- It forwards only Atribu's own
atb_vidcookie. Your app's other cookies (auth sessions, carts) never leave your server. - The proxy secret stays server-side. It is never sent to browsers, and Atribu stores only a fingerprint of it.
- Nothing here is cached — not by Next.js's own fetch cache, not by any CDN in front of your deployment. A cached collect response, or a cached script carrying another visitor's id, would be a real bug, so every outbound call the route makes uses
cache: "no-store"and every response it returns setsCache-Control: no-store(the tracker script itself isprivate, max-age=300— short-lived and markedVary: Cookieso it is never shared across visitors).
Reference
createAtribuProxy(config) returns { GET, POST } — the two functions your route file re-exports.
| Option | Required | Default | Meaning |
|---|---|---|---|
secret | Yes | — | Your Profile's proxy secret, from an env var. |
path | No | /atb | The path this route is mounted at. Must match the folder (app/atb/[...slug] → /atb) and the "Collector path on your site" you saved in Atribu. |
origin | No | https://www.atribu.app | Atribu origin to proxy to. |
cookieDomain | No | — | A parent domain to share atb_vid across subdomains. |
The module has no dependency on next — it is written against the standard Request/Response objects a Route Handler already uses, so next/server's NextRequest/NextResponse (which extend them) work too, and nothing framework-specific is required to call it in a test.