PostHog Next.js Setup: Analytics in One Session (2026)
A founder-friendly PostHog Next.js setup guide — the instrumentation-client install, reverse proxy, what to track for indie SaaS, and the Claude Code prompt.
· Justin Boggs

Photo by Stephen Dawson on Unsplash
PostHog Next.js setup in 2026 takes one package install, one instrumentation-client.ts file, and two environment variables — that's the whole client-side integration on Next.js 15.3+. Add a reverse proxy so ad blockers don't eat your events, call posthog.identify() after login, and you have product analytics, session replays, and feature flags running on a free tier of one million events a month. This post walks through the exact setup, what an indie SaaS should actually track in its first months, and the copy-paste prompt that lets Claude Code or Cursor do the wiring for you.
TL;DR
- Install
posthog-js, initialize it ininstrumentation-client.tsat your project root, addNEXT_PUBLIC_POSTHOG_TOKENandNEXT_PUBLIC_POSTHOG_HOSTto.env.local.- Set up a reverse proxy (PostHog's managed one is free) — without it, tracking blockers silently drop a chunk of your events.
- Call
posthog.identify(userId)after login or events stay anonymous.- The free tier covers 1M events, 5K session replays, and 1M feature-flag requests per month, per PostHog's pricing page.
- Track 5–10 events tied to your funnel, not 50 tied to your curiosity.
Why PostHog for an indie SaaS
Google Analytics tells you about traffic. PostHog tells you about behavior — which features people use, where they drop out of your signup funnel, and (with session replay) literally what they saw when they got confused and left. For a founder doing everything alone, that last one substitutes for the user-research department you don't have.
PostHog's bundling is the practical argument. One SDK gets you product analytics, web analytics, session replay, feature flags, A/B testing, error tracking, and surveys. Each of those is a separate vendor in the traditional stack — Mixpanel plus Hotjar plus LaunchDarkly plus a survey tool — each with its own script tag, billing relationship, and data silo. PostHog's pricing page says more than 90% of companies use it free, and the free tier is structured to make that plausible: 1 million analytics events, 5,000 session replays, 1 million feature-flag requests, 100,000 error logs, and 1,500 survey responses per month. Past the free allowance, billing is usage-based — the 1–2 million event band runs $0.00005 per event, with rates dropping as volume grows.
For Coding Capybaras, PostHog is the analytics layer in the Next.js + Supabase + Stripe + Resend stack. It answers the questions Stripe and Supabase can't: not "how many users do I have" but "what did the ones who upgraded do differently."
One honest caveat: PostHog's UI is dense. It's a power tool, and the first session in the dashboard feels like sitting in a cockpit. The fix is to ignore 90% of it at first — set up two funnels and a dashboard, and expand only when a real question forces you to. If you want something radically simpler, Plausible and Fathom are excellent privacy-first page-analytics tools — but they answer traffic questions, not product questions, and you'd still need something else for funnels and replays.
The client-side setup (Next.js 15.3+)
The integration got dramatically simpler when Next.js added the instrumentation-client convention. Everything below comes from PostHog's official Next.js docs, which is the source to check when versions drift.
Step 1 — install the SDK:
pnpm add posthog-js
Step 2 — add your environment variables to .env.local (and to Vercel's dashboard for production). Your project token lives in PostHog under Project Settings:
NEXT_PUBLIC_POSTHOG_TOKEN=phc_your_token_here
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
The NEXT_PUBLIC_ prefix is required — these values are read in the browser. The project token is a write-only public key, so shipping it client-side is by design (it can send events, not read your data). Your PostHog personal API keys are a different story: those are secrets and belong in .env.local only, never in client code.
Step 3 — create instrumentation-client.ts at the root of your project (alongside app/, not inside it):
import posthog from 'posthog-js'
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_TOKEN!, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
defaults: '2026-01-30',
})
That's the entire client integration on Next.js 15.3+. No provider component wrapping your layout, no useEffect initialization dance — the older providers.tsx pattern still works for pages-router apps, but if you're on a current App Router project, the instrumentation file is the supported path. The defaults date pins you to a snapshot of PostHog's recommended config so behavior doesn't shift under you when the SDK updates.
Pageviews are captured automatically from here, including client-side route changes. To capture a custom event from any client component, import the singleton and call it:
'use client'
import posthog from 'posthog-js'
<button onClick={() => posthog.capture('boilerplate_download_clicked')}>
Download
</button>
Step 4 — identify users. This is the step founders skip and regret. After a successful login or signup, call:
posthog.identify(user.id, { email: user.email })
Without it, every visitor is an anonymous ID, and you can't connect the marketing-site visit to the signup to the upgrade. PostHog's docs flag identification as required for a reason — it's what links events, session replays, and error tracking to the same human. Use your database user ID as the distinct ID, not the email (emails change).
The two production details that separate working setups from broken ones
Set up a reverse proxy. Tracking blockers and some browser defaults block requests to *.posthog.com. The fix is routing events through your own domain. PostHog offers a managed reverse proxy free for all Cloud users, or you can do it with Next.js rewrites in next.config.ts — a three-line change that routes /ingest/* to PostHog's servers. Either way, do this before you trust your numbers: without a proxy, you're undercounting, and the missing users skew toward the technical audience most likely to be your early adopters.
Server-side events need posthog-node and a shutdown call. For events that happen outside the browser — a Stripe webhook marking a subscription active, a cron job completing — use the server SDK:
// lib/posthog-server.ts
import { PostHog } from 'posthog-node'
export default function PostHogClient() {
return new PostHog(process.env.NEXT_PUBLIC_POSTHOG_TOKEN!, {
host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
flushAt: 1,
flushInterval: 0,
})
}
The flushAt: 1, flushInterval: 0 settings exist because serverless functions are short-lived: PostHog normally batches events for efficiency, and a Vercel function can return (and freeze) before the batch sends. Flushing immediately — and calling await posthog.shutdown() when you're done — guarantees the event leaves the building. Losing server-side events to this is the single most common PostHog-on-Vercel bug, and it's invisible: nothing errors, events just never arrive.
The payment events are the ones most worth capturing server-side, because the client may never see them — a user can close the tab before Stripe redirects back. If you've wired Stripe webhooks already, adding a subscription_started capture inside the webhook handler is three lines.
What an indie SaaS should actually track
The mistake is tracking everything. Events are cheap; attention isn't. Fifty events produce a dashboard you stop reading. Here's a starting schema that fits in your head:
| Event | Where it fires | Why it matters |
| --- | --- | --- |
| $pageview | Automatic | Traffic baseline, content performance |
| signup_completed | Client, after auth | Top of your product funnel |
| activation_reached | Client or server | Your "aha moment" — define it explicitly |
| subscription_started | Server, Stripe webhook | Revenue, conversion funnel floor |
| subscription_cancelled | Server, Stripe webhook | Churn signal |
| core_action_performed | Client | Whatever your product's verb is |
The one that requires thought is activation_reached. For Coding Capybaras it's "downloaded the boilerplate and completed the journey's first stage" — the point where a user has experienced the product's actual value. Define yours, instrument it, and build your first funnel as signup → activation → subscription. That single funnel answers the only early-stage question that matters: where do people fall out?
Two PostHog features quietly do heavy lifting here. Autocapture records clicks, form submissions, and inputs without explicit instrumentation — imperfect, but it means you have retroactive data for questions you didn't know to ask. And session replay at 5,000 free recordings a month means you can watch every confused user at indie scale. The first time you watch someone rage-click a button you thought was obviously a link, the entire setup pays for itself.
Resist building a metrics empire in week one. A signup funnel, a weekly-active-users trend, and replays of dropped sessions cover the first hundred customers.
The ritual matters as much as the schema. Pick a fixed slot — mine is Monday morning, before email — and spend fifteen minutes on three questions: did the funnel conversion move, did weekly actives move, and is there a dropped session worth watching? Write down one sentence about each. That's the entire practice. Analytics that get reviewed weekly change decisions; analytics that get reviewed "when I remember" become an expensive screensaver. The numbers will be small and noisy at first — at fifty users, one person churning moves your percentages embarrassingly — so watch directions and patterns, not single data points, and resist reacting to any movement you haven't seen twice.
A note on naming, because it's cheap to do now and miserable to fix later: pick a convention (object_action, lowercase, past tense — signup_completed, subscription_started) and put the list in a file in your repo. Six months from now, when you're staring at Signup, sign_up, and signupCompleted as three separate events because three different coding sessions guessed differently, the funnel that should take two minutes to build will take an evening of merging. AI assistants make this worse by default — each session invents plausible names — which is exactly why the event schema belongs in your repo where the AI can read it, not in your head.
The Claude Code prompt that does the wiring
This is the kind of task AI coding tools handle well: official docs exist, the file changes are mechanical, and verification is concrete. Here's the prompt I'd paste into Claude Code, Cursor, or Cowork — adjust the first line to your stack:
Add PostHog analytics to my Next.js 15 App Router project.
1. Install posthog-js and posthog-node.
2. Create instrumentation-client.ts at the project root that initializes
posthog-js with NEXT_PUBLIC_POSTHOG_TOKEN and NEXT_PUBLIC_POSTHOG_HOST
from env vars, using defaults: '2026-01-30'.
3. Add both env vars to .env.local with placeholder values and to
.env.example.
4. Set up a reverse proxy using Next.js rewrites so events go through
/ingest on my domain instead of directly to posthog.com.
5. Create lib/posthog-server.ts exporting a PostHogClient factory for
posthog-node with flushAt: 1 and flushInterval: 0.
6. Call posthog.identify with the user's ID and email after successful
login in my auth flow — find where sessions are established and add
it there.
7. Do not add any custom events yet. Show me a list of the files you
changed and how to verify events arrive in PostHog.
The "do not add custom events yet" line is deliberate. Event schemas deserve a human decision, and AI assistants get enthusiastic — left unprompted, they'll instrument every button you have and a few you don't. Review the diff, add your token, run the dev server, and check PostHog's Activity view for your first pageview. If events don't show up, the culprit is almost always an env var typo or an overzealous ad blocker on your own browser — verify in an incognito window before debugging code. The Claude Code for non-developers guide covers the general review workflow if AI-assisted changes like this are new territory.
PostHog also ships its own LLM-powered setup wizard (npx @posthog/wizard@latest) — the same idea, run from their side. Either path lands in the same place; the prompt above just keeps your boilerplate's conventions intact.
If you've already added Sentry error tracking, note the division of labor: Sentry tells you why it broke, PostHog tells you what users do. They overlap at the edges (PostHog has error tracking now, Sentry has session replay), but most indie stacks run both on free tiers and let each do its specialty.
Privacy, EU hosting, and the cookie banner question
Analytics decisions are privacy decisions, and it's worth making them deliberately on day one rather than retrofitting under pressure.
The first choice is hosting region. PostHog Cloud runs in both the US (https://us.i.posthog.com) and the EU (https://eu.i.posthog.com) — you pick at project creation, and the host goes in your NEXT_PUBLIC_POSTHOG_HOST variable. If your audience is meaningfully European, the EU instance keeps event data on EU infrastructure, which simplifies the GDPR conversation considerably. Switching regions later means a new project, so it's one of the few setup decisions worth pausing on.
The second choice is session replay hygiene. PostHog masks all input fields in recordings by default, which covers passwords and card fields, but audit what your replays actually show before you rely on them for support — anything rendered as text on screen (account emails on a settings page, API keys in a dashboard) is visible unless you mask it. Adding the ph-no-capture class to sensitive elements excludes them from recordings. Five minutes of masking during setup beats discovering customer data in a replay you've already shared with a contractor.
Finally, identified user data is deletable. When a customer asks you to erase their data — and eventually one will — PostHog supports deleting a person and their events from the dashboard or API. Knowing that workflow exists before you need it is the difference between answering a deletion request in ten minutes and panicking about it for a weekend.
Frequently asked questions
Is PostHog really free for an indie SaaS?
For most pre-scale products, yes. The free tier resets monthly at 1 million events, 5,000 session replays, and 1 million feature-flag requests, and PostHog states that more than 90% of its companies pay nothing. You also have to add a card and set a billing limit only when you exceed free volume — and you can set that limit to $0 to hard-stop spending.
Do I need the reverse proxy, or is it optional?
Treat it as required. A meaningful share of browsers block third-party analytics domains, and the loss isn't random — technical users block more. PostHog's managed reverse proxy is free for Cloud customers, and the Next.js rewrites alternative takes minutes. Set it up before you make decisions from the data, or your numbers will quietly lie to you.
Should I use PostHog or Google Analytics for my SaaS?
Both have a place, but if you pick one, pick PostHog for a product. GA4 is built for marketing attribution; PostHog is built for product behavior — funnels tied to identified users, session replays, and feature flags. PostHog's web analytics module covers the basic traffic dashboard GA would give you anyway.
What's the difference between autocapture and custom events?
Autocapture automatically records clicks, inputs, and pageviews with zero code — useful as a safety net and for retroactive questions. Custom events are explicit posthog.capture() calls with names you chose, and they're what your funnels and dashboards should be built on. Run autocapture on, but treat your 5–10 named events as the source of truth.
Why aren't my server-side events showing up in PostHog?
Almost always batching: posthog-node queues events, and serverless functions terminate before the queue flushes. Initialize with flushAt: 1, flushInterval: 0 and call await posthog.shutdown() after capturing. If client events arrive but server events don't, this is the bug.
Can PostHog replace my feature flag and A/B testing tools?
For an indie SaaS, yes. Feature flags ship in the same SDK you've already installed, with 1 million flag requests free per month, and experiments are built on top of them. Dedicated flag platforms earn their keep at enterprise scale; at founder scale, consolidating on one tool is the right trade.
Conclusion
PostHog Next.js setup is one of the highest-leverage hours an indie founder can spend: a package, an instrumentation file, a proxy, and an identify call gets you analytics, replays, and feature flags before lunch — free at your scale. The discipline that makes it valuable comes after: define your activation event, build the signup → activation → subscription funnel, and watch session replays when the funnel surprises you. If you'd rather start from a codebase where this is already wired, Coding Capybaras ships with PostHog integration in the free boilerplate, and the PostHog marketplace guide has the exact prompt to add it to an existing Next.js + Supabase + Stripe app.