Installation guides
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.
app/layout.tsx
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>
);
}pages/_app.tsx
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.
'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;
}<ProductLenseIdentify userId={session.user.id} />Use the same id you store on the customer in your billing provider; see how payments are attributed.
Custom events
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:
<Script id="productlense-stub" strategy="beforeInteractive">
{`window.productlense=window.productlense||function(){(window.productlense.q=window.productlense.q||[]).push(arguments)}`}
</Script>See custom events for naming rules.
Verify your installation
- Deploy, then open your site in a normal browser window.
- Open the Live page in the ProductLense dashboard. Your visit appears within a few seconds.
- In the browser's developer tools, the Network tab shows a
POSTtohttps://api.productlense.app/api/ewith status204. 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.