Product analytics for Next.js
Pug’s Web SDK is framework-agnostic JavaScript, there’s no separate Next.js package to learn. Mount it once in a client component from your App Router root layout, then add one effect so client-side navigations keep counting.
Add Pug to Next.js
Four steps, start to first insight. Everything below uses the Web SDK exactly as it ships. There is no Next.js wrapper in between.
-
Install the Web SDK
One package covers every browser framework. It ships compiled JavaScript plus type definitions for bundlers, so Next.js needs no extra config.
npm install @pug-sh/browser # or: pnpm add @pug-sh/browser · yarn add @pug-sh/browser · bun add @pug-sh/browser -
Initialize from a client component
init() must run in the browser, not during SSR, so put it in a small "use client" component and render it once inside <body> in app/layout.tsx. Both values are public (the key is write-scoped and safe to ship in client code), so NEXT_PUBLIC_ env vars are the right home for them.
app/pug-provider.tsx'use client' import { useEffect } from 'react' import { init } from '@pug-sh/browser' export function PugProvider() { useEffect(() => { init(process.env.NEXT_PUBLIC_PUG_PROJECT_ID!, { apiKey: process.env.NEXT_PUBLIC_PUG_PUBLIC_KEY!, }) }, []) return null } -
Keep counting App Router navigations
Autocapture derives page views from the History API. The App Router navigates without a History push, so it fires page_view on the first load and then goes quiet, send the rest yourself from a usePathname() effect. This is the one piece of Next.js-specific wiring Pug needs.
app/pug-page-views.tsx'use client' import { useEffect } from 'react' import { usePathname } from 'next/navigation' import { track } from '@pug-sh/browser' export function PugPageViews() { const pathname = usePathname() useEffect(() => { track('page_view') }, [pathname]) return null } -
Identify people, and trust your revenue events
Call identify() once you know who someone is, the anonymous session merges into their profile. Events that must be right regardless of the browser belong on the server: send those from a route handler with the Node SDK, naming the same user id.
app/api/checkout/route.tsimport { Pug } from '@pug-sh/node' const pug = new Pug({ apiKey: process.env.PUG_PRIVATE_KEY! }) // prv_… - server only export async function POST(req: Request) { const { userId, amount } = await req.json() pug.track(userId, 'order_completed', { amount, currency: 'USD' }) return Response.json({ ok: true }) }
What to watch for in Next.js
The specifics that decide whether the numbers you see are the numbers that happened.
App Router page views need one effect
The History API is what autocapture listens to, and App Router navigation does not push through it. Without the usePathname() effect above you get a page_view on entry and nothing after, the most common reason a Next.js funnel looks broken.
Server Components never run the tracker
They render on the server, where there is no browser session to attribute to. Keep init() in a client component, and use the Node SDK for anything that happens in a route handler, server action, or webhook.
Two keys, two homes
The public key (pub_…) is write-scoped and belongs in NEXT_PUBLIC_ vars. The private key (prv_…) also authorizes reads, keep it server-side only. The Node SDK refuses to construct with anything but a prv_ key.
Cookieless until someone answers
The Web SDK starts in cookieless consent by default: events flow with a server-derived, daily-rotating identity and nothing is written to the device until the visitor actually chooses. Pass trackingConsent to change that.
Product analytics in your Next.js app
-
Autocapture, six trackers
Page views, clicks, scrolls, form starts and submits, rage clicks, and dead clicks: all on after one init(), or narrowed with a per-listener allowlist.
-
One profile per person
identify() merges the anonymous session into the signed-in profile and keeps it joined across devices and later visits.
-
Your own events
track() takes a name and typed properties, with a generated list of well-known event kinds so your names match what the insights expect.
-
Every insight type
Trends, funnels, retention cohorts, segmentation, user flows, and top-K run over the same events, no separate instrumentation per report.
Read it in the docs
The Web SDK reference goes deeper than a setup page can: every option, every event, and the edge cases.
Pug + Next.js: common questions
Do I need a Next.js-specific package?
No. And there does not need to be one. Pug ships a single Web SDK (TypeScript/JavaScript) that runs in any browser framework, Next.js included. You install the same @pug-sh/browser package and initialise it once from a client component.
Does it work with the App Router and Server Components?
Yes. Autocapture is a browser concern, so initialise the SDK in a small "use client" component mounted in your root layout. Server Components render on the server and don’t run the tracker themselves; pair the Web SDK with the Node SDK if you also want authoritative server-side events.
Why do page views stop after the first load?
Because the App Router navigates without a History push, and that is what autocapture listens to. Add the usePathname() effect above: it calls track('page_view') on every route change, which is the same event autocapture would have sent. The Pages Router and ordinary SPA routers push through History, so they need no equivalent.
Where do I put the project ID and keys?
The project ID and public key go in NEXT_PUBLIC_ environment variables. The public key is write-scoped (events and identify only) and designed to ship in client code. The private key is a different key entirely: it also authorizes reads, so it stays in server-only env vars and is used by the Node SDK.
Can I self-host the analytics backend?
Yes. Pug is open source under AGPL-3.0. Run the whole stack on your own infrastructure, or use the free cloud during open beta. Either way your Next.js app points the SDK at your endpoint with the endpoint option.
Add Pug to your Next.js app.
Open-source product analytics with unified profiles. Self-host under AGPL-3.0, or use the free cloud during open beta.
Questions? Email hello@pug.sh