Atribu
Tracking

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 sitehttps://yourstore.com
  • Collector path on your sitehttps://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):

app/atb/[...slug]/route.ts
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:

.env.local
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.

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:

app/atb/[...slug]/route.ts
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_vid cookie. 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 sets Cache-Control: no-store (the tracker script itself is private, max-age=300 — short-lived and marked Vary: Cookie so it is never shared across visitors).

Reference

createAtribuProxy(config) returns { GET, POST } — the two functions your route file re-exports.

OptionRequiredDefaultMeaning
secretYesYour Profile's proxy secret, from an env var.
pathNo/atbThe 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.
originNohttps://www.atribu.appAtribu origin to proxy to.
cookieDomainNoA 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.


Next steps

On this page