# Proxy DataFast through your Next.js domain so ad blockers stop eating visits

> Blockers match the datafa.st hostname. Serve the script and the events endpoint from your own origin with two route handlers, and forward the visitor's IP.

Your server logs show 1,000 visits. DataFast shows 780. The gap does not move.
That is not a bug in the script. It is a blocklist.

## Why it happens

Blocklists match hostnames. `datafa.st` is on the common privacy lists, so
uBlock Origin, Brave and friends drop the request before it leaves the browser.
Nothing throws. Nothing logs. The visitor just never exists.

The people who block skew technical. If you sell to developers, the traffic you
lose is the traffic you most wanted to measure.

## The fix: same-origin paths

If the page loads `/js/script.js` from your own domain and posts to
`/api/events` on your own domain, there is no third-party hostname to match.
Your server forwards both to DataFast, from a data centre, where no blocker
runs.

DataFast supports this out of the box. When the script is loaded from anywhere
other than `datafa.st`, it posts events to `/api/events` on the page's origin.
You can point it elsewhere with `data-api-url`.

## Option 1: rewrites (DataFast's docs)

```ts
// next.config.ts
async rewrites() {
  return [
    { source: "/js/script.js", destination: "https://datafa.st/js/script.js" },
    { source: "/api/events", destination: "https://datafa.st/api/events" },
  ];
}
```

Short, and it works. It is also invisible: you cannot log it, and you cannot
return something harmless when the upstream is down.

## Option 2: two route handlers

The script:

```ts
// src/app/js/script.js/route.ts
export const dynamic = "force-dynamic";

export async function GET() {
  try {
    const upstream = await fetch("https://datafa.st/js/script.js", {
      signal: AbortSignal.timeout(5_000),
      next: { revalidate: 3600 },
    });
    if (!upstream.ok) throw new Error(String(upstream.status));
    return new Response(await upstream.text(), {
      headers: {
        "content-type": "application/javascript; charset=utf-8",
        "cache-control": "public, max-age=3600",
      },
    });
  } catch {
    // An empty script, not an error page. The site keeps working.
    return new Response("", { headers: { "content-type": "application/javascript" } });
  }
}
```

Yes, the folder is literally named `script.js`. The App Router allows dots in
segment names.

The events:

```ts
// src/app/api/events/route.ts
import type { NextRequest } from "next/server";

export async function POST(request: NextRequest) {
  const headers = new Headers({ "content-type": "application/json" });
  for (const name of ["user-agent", "origin", "referer"]) {
    const value = request.headers.get(name);
    if (value) headers.set(name, value);
  }
  const ip = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim();
  if (ip) {
    headers.set("x-forwarded-for", ip);
    headers.set("x-real-ip", ip);
  }

  try {
    const upstream = await fetch("https://datafa.st/api/events", {
      method: "POST",
      headers,
      body: await request.text(),
      signal: AbortSignal.timeout(5_000),
    });
    return new Response(await upstream.text(), { status: upstream.status });
  } catch {
    return new Response(null, { status: 204 });
  }
}
```

Then the tag:

```tsx
<Script
  src="/js/script.js"
  data-website-id={process.env.NEXT_PUBLIC_DATAFAST_WEBSITE_ID}
  data-domain="example.com"
  data-api-url="/api/events"
  strategy="afterInteractive"
/>
```

## The three details that break it

**Forward the IP.** Behind your proxy, DataFast sees your server. Skip the
forwarded headers and every visitor comes from `iad1`, on the same browser.
DataFast's own troubleshooting names this: "all visitors showing from the same
location".

**Forward the user agent.** Browser, OS and device come from it. Without it,
everything is "unknown".

**Answer inert on failure.** An empty script and a `204`. A red 500 in a
visitor's console, for analytics, is a bad trade.

## Already have `/api/events`?

Pick another path for the handler, say `/api/df-events`, and set
`data-api-url="/api/df-events"` on the tag. Keep it boring. A path with
"analytics" or "tracking" in it is exactly what a blocklist adds next.

## The managed alternative

DataFast also runs a managed proxy: a CNAME such as `a.example.com` pointing at
DataFast, with SSL handled for you. Then `src` and `data-api-url` both use full
URLs on that subdomain. No code, one DNS record. Pick it if you would rather
not own two route handlers.

## Check it works

1. Deploy. Open the site with uBlock Origin on.
2. DevTools, Network: `script.js` and `events` hit your domain and return 200.
   Nothing to `datafa.st`.
3. DataFast's realtime view shows you, in your real city.

Proxying changes a hostname. It does not change what you collect or what
consent you need.

---

Agentic Boilerplate: A Next.js repo your agent already knows. Free during launch, then $99 once.

- Site map for agents: https://agenticboilerplate.com/llms.txt
- Public API: https://agenticboilerplate.com/openapi.json
- Contact: agenticstudio@gmail.com
