# Next.js

Serve the ProductLense script from your Next.js domain with a rewrite, and forward events through a route handler on the Scale plan.

Next.js can serve the script from your own domain with one rewrite. On the Scale plan, a route handler forwards events too, so every request the browser makes is first-party. Read the [proxy overview](/docs/proxy/overview) first for what each option fixes.

## Serve the script

```js title="next.config.js"
/** @type {import('next').NextConfig} */
module.exports = {
  async rewrites() {
    return [{ source: '/js/a.js', destination: 'https://api.productlense.app/a.js' }];
  },
};
```

Then load it from your path and keep events going to ProductLense with `data-api`:

```tsx title="app/layout.tsx" {9-13}
import Script from 'next/script';
import type { ReactNode } from 'react';

export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script
          src="/js/a.js"
          data-site="SITE_ID"
          data-api="https://api.productlense.app"
          strategy="afterInteractive"
        />
      </body>
    </html>
  );
}
```

## Forward events (Scale plan)

1. Create an API key with the `events` scope (see [Authentication and API keys](/docs/api/authentication)) and store it as `PL_API_KEY` in your environment.
2. Add the route handler below.
3. Change the tag to `data-api="https://example.com/m"`, using your own domain.

```ts title="app/m/api/e/route.ts"
import type { NextRequest } from 'next/server';

type Event = Record<string, unknown>;

export async function POST(req: NextRequest) {
  const text = await req.text();
  if (text.length > 8192) return new Response(null, { status: 413 });
  let body: { b?: Event[] } & Event;
  try {
    body = JSON.parse(text);
  } catch {
    return new Response(null, { status: 400 });
  }
  // The first x-forwarded-for entry is the visitor on Vercel and most hosts; adjust for your load balancer.
  const ip = req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? '';
  const extra = { ip, ua: req.headers.get('user-agent') ?? '' };
  const out = body.b ? { ...body, b: body.b.map((e) => ({ ...e, ...extra })) } : { ...body, ...extra };
  const r = await fetch('https://api.productlense.app/api/v1/events', {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.PL_API_KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify(out),
  });
  return new Response(null, { status: r.status });
}
```

The browser sends batches as `text/plain` through `sendBeacon`, so the handler reads the raw text instead of `req.json()`.

> [!IMPORTANT]
> Do not replace the route handler with a second rewrite to `https://api.productlense.app/api/e`. A rewrite sends your server's IP address, so every visitor collapses into one and every country becomes your server's region.

## Check it works

1. Open `https://example.com/js/a.js`: it returns JavaScript, not your 404 page.
2. Load any page with the developer tools open. The Network tab shows `a.js` from your domain and a `POST` to `api/e` with status `204`.
3. Open the Live page in the ProductLense dashboard. Your visit appears within a few seconds, with your real country.
