# Next.js

Add ProductLense to a Next.js app with next/script in the root layout, then identify signed-in users from a client component.

In Next.js, load ProductLense with the `next/script` component and `strategy="afterInteractive"`. Put it in the root layout (App Router) or in `_app.tsx` (Pages Router) so it runs on every page. Client-side navigation is recorded through the history API, so no router hooks are needed.

## Add the script

Replace `SITE_ID` with the id from Settings → Sites in the ProductLense dashboard.

```tsx title="app/layout.tsx" tab="App Router" {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="https://api.productlense.app/a.js"
          data-site="SITE_ID"
          strategy="afterInteractive"
        />
      </body>
    </html>
  );
}
```

```tsx title="pages/_app.tsx" tab="Pages Router" {7-11}
import Script from 'next/script';
import type { AppProps } from 'next/app';

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Script
        src="https://api.productlense.app/a.js"
        data-site="SITE_ID"
        strategy="afterInteractive"
      />
      <Component {...pageProps} />
    </>
  );
}
```

Do not use `strategy="worker"`: the script must run on the main thread, where it reads its own tag through `document.currentScript`.

## Identify users

Render a small client component wherever the signed-in user is known, for example in a layout that loads the session. It uses optional chaining so nothing fails if the script has not loaded yet or is blocked.

```tsx title="app/productlense-identify.tsx" {12}
'use client';
import { useEffect } from 'react';

declare global {
  interface Window {
    productlense?: (...args: unknown[]) => void;
  }
}

export function ProductLenseIdentify({ userId }: { userId?: string }) {
  useEffect(() => {
    if (userId) window.productlense?.('identify', userId);
  }, [userId]);
  return null;
}
```

```tsx title="app/(app)/layout.tsx"
<ProductLenseIdentify userId={session.user.id} />
```

Use the same id you store on the customer in your billing provider; see [how payments are attributed](/docs/revenue/attribution).

## Custom events

```tsx
window.productlense?.('event', 'Upgrade clicked', { plan: 'growth' });
```

`afterInteractive` loads the script after hydration, so an event fired during the first render may arrive before `window.productlense` exists. Call events from handlers and effects, or add the queue stub with `strategy="beforeInteractive"` in the root layout if you need calls to be queued:

```tsx title="app/layout.tsx"
<Script id="productlense-stub" strategy="beforeInteractive">
  {`window.productlense=window.productlense||function(){(window.productlense.q=window.productlense.q||[]).push(arguments)}`}
</Script>
```

See [custom events](/docs/track/custom-events) for naming rules.

## Verify your installation

1. Deploy, then open your site in a normal browser window.
2. Open the Live page in the ProductLense dashboard. Your visit appears within a few seconds.
3. In the browser's developer tools, the Network tab shows a `POST` to `https://api.productlense.app/api/e` with status `204`. Click a link to another page: a second pageview follows.

`next dev` runs on `localhost`, where nothing is sent unless you add `data-dev="true"` to the Script component. Ad blockers in your own browser may block the request. If the visit does not appear, follow [Verify your installation](/docs/install/verify).
