# Proxy with Next.js

> Serve the MetricRaven script and events from your Next.js app with one App Router route handler.

Source: https://metricraven.com/docs/proxy-nextjs  
Markdown: https://metricraven.com/docs/proxy-nextjs.md  
Section: Proxy

One route handler in the App Router serves the script and relays events.

## Set it up

1. **Add a route handler**

   ```typescript title="app/a/[kind]/route.ts"
   // app/a/[kind]/route.ts — Next.js App Router
   import { NextRequest } from "next/server";

   async function proxy(req: NextRequest, context: { params: Promise<{ kind: string }> }) {
     const { kind } = await context.params;
     const script = kind === "script" && req.method === "GET";
     const event = kind === "event" && req.method === "POST";
     if (!script && !event) return new Response("Not found", { status: 404 });
     const headers = new Headers();
     for (const name of ["content-type", "origin", "user-agent"]) {
       const value = req.headers.get(name);
       if (value) headers.set(name, value);
     }
     // Trust these only if your hosting provider overwrites them.
     const ip = req.headers.get("cf-connecting-ip") || req.headers.get("x-forwarded-for")?.split(",")[0]?.trim();
     if (ip) headers.set("x-visitor-ip", ip);
     for (const [source, target] of [["cf-ipcountry", "x-visitor-country"], ["cf-region", "x-visitor-region"], ["cf-ipcity", "x-visitor-city"]]) {
       const value = req.headers.get(source);
       if (value) headers.set(target, value);
     }
     const upstream = await fetch(script ? "https://metricraven.com/js/script.js" : "https://metricraven.com/api/v1/collect", {
       method: req.method, headers, body: event ? await req.arrayBuffer() : undefined,
       cache: "no-store", signal: AbortSignal.timeout(15000),
     });
     return new Response(upstream.body, { status: upstream.status, headers: {
       "Content-Type": upstream.headers.get("content-type") || "application/octet-stream",
       "Cache-Control": script ? "public, max-age=3600" : "no-store",
     }});
   }
   export { proxy as GET, proxy as POST };
   ```

   On Vercel, `x-forwarded-for` is set by the platform. Behind Cloudflare, `cf-connecting-ip` and Cloudflare's visitor location headers are used when present.

2. **Load the tracker with next/script**

   In your root layout, set `src="/a/script"` and `data-api="/a/event"` on the existing `Script` component.

3. **Point your script tag at your own paths**

   Replace your MetricRaven tag with this one. `src` loads the tracker from your domain and `data-api` sends its events there too.

   ```html title="Your website's shared layout"
   <script defer src="/a/script" data-api="/a/event" data-site-id="YOUR_SITE_ID"></script>
   ```

> **Verify it works**
>
> Open your live site. In the browser's Network panel, `/a/script` loads the tracker and `/a/event` answers with `ok: true`. Nothing goes to `metricraven.com` from the browser.
>
> In **Traffic**, your own visit should show your real country and city. If every visit appears to come from your server's or host's location, the visitor IP isn't being forwarded.

## Notes

- The handler reads `x-forwarded-for`. Trust it only if your host overwrites it; a self-hosted Next.js server without a proxy in front should use the socket address instead.
- If you use Next.js middleware with a matcher for all paths, exclude `/a/` so it doesn't redirect or rewrite the tracker's requests.

How the proxy works and which headers it must send: [Use your own domain](https://metricraven.com/docs/proxy).

## Details

- The proxy forwards only `Content-Type`, `Origin`, `User-Agent` and the visitor's IP as `x-visitor-ip`. Your site's cookies and auth headers never leave your server.
- `Origin` matters: MetricRaven checks it against your site's allowed domains. A missing or wrong origin is refused with `domain_not_allowed`.
- MetricRaven allows about 120 events per minute per visitor IP. Without `x-visitor-ip`, all of your visitors share the proxy's IP, so busy sites lose events as well as locations.
- A proxy doesn't guarantee that every blocker will allow tracking.
