Plainform

Mobile Sidebar

Mobile Sidebar

How to Use PostHog Analytics in a SaaS Product

14 Jun 2026
9 minute read
R
Ronald SolticzkiBackend Engineer
How to Use PostHog Analytics in a SaaS Product

PostHog analytics is an open-source product analytics platform that gives you event tracking, session recording, feature flags, A/B testing, and funnels — all in one tool. For a SaaS product, it is one of the most practical choices available: self-hostable if you need it, hosted in the EU if you need GDPR compliance, and deeply integrated with the React ecosystem.

This post covers how PostHog analytics is set up in Plainform — a production-ready Next.js SaaS starter. You will see the actual initialization code, how events are captured, how users are identified, and how the EU reverse proxy works to keep analytics data away from ad blockers and compliant with GDPR.

Why PostHog for a SaaS Product

Most analytics tools are built for marketing teams. PostHog analytics is built for product teams. The events you track are the same ones you use to build funnels, replay sessions, run A/B tests, and control feature flags. Everything lives in one place, with one SDK.

For a Next.js SaaS app specifically, PostHog has first-class React support, autocapture for page views and clicks, and a clean TypeScript API. The EU-hosted cloud option (eu.i.posthog.com) makes GDPR compliance straightforward without self-hosting.

The alternative — wiring up Mixpanel, a separate session recorder, and a feature flag service — costs more, requires more integrations, and gives you fragmented data across three dashboards.

Installing PostHog

Install the main library and the React bindings:

npm install posthog-js @posthog/react

You will need two environment variables:

NEXT_PUBLIC_POSTHOG_KEY=phc_...
NEXT_PUBLIC_POSTHOG_HOST=https://eu.i.posthog.com

Both are public client-side variables — they are safe to expose in the browser. The API key identifies your PostHog project. The host points to the EU cloud if you need EU data residency, or the US cloud (https://app.posthog.com) if you do not.

Initializing PostHog in Next.js

Using instrumentation-client.ts

The cleanest way to initialize PostHog analytics in a Next.js App Router project is through instrumentation-client.ts. This file runs once on the client side when the app loads, before any page renders.

Here is how Plainform does it:

// instrumentation-client.ts
import posthog from 'posthog-js';
import { env } from '@/env';

posthog.init(env.NEXT_PUBLIC_POSTHOG_KEY!, {
  api_host: '/relay-sdK3',
  defaults: '2025-05-24',
  cookieless_mode: 'on_reject',
});

A few things worth explaining here:

  • api_host: '/relay-sdK3' points to a local Next.js rewrite path rather than directly to PostHog's servers. This is the reverse proxy setup covered below.
  • defaults: '2025-05-24' pins PostHog's default configuration to a specific date, so behaviour does not change unexpectedly when PostHog updates their defaults.
  • cookieless_mode: 'on_reject' runs PostHog without cookies until the user accepts the cookie banner. This is how GDPR compliance is handled without blocking all analytics by default.

Why not a Provider component

Some PostHog guides recommend wrapping your app in a PostHogProvider component in layout.tsx. That works, but it requires adding a Client Component wrapper to your root layout, which means the entire layout tree must be client-rendered.

The instrumentation-client.ts approach avoids that. The file runs at the framework level, the root layout stays a Server Component, and PostHog is still available everywhere in the app through the posthog-js singleton.

Setting Up the EU Reverse Proxy

PostHog analytics requests are frequently blocked by ad blockers and privacy extensions. The standard fix is to route PostHog traffic through your own domain — a reverse proxy — so the requests look like first-party traffic.

Configuring Next.js rewrites

In next.config.mjs, add rewrites that forward PostHog paths to the EU cloud:

async rewrites() {
  return [
    {
      source: '/relay-sdK3/static/:path*',
      destination: 'https://eu-assets.i.posthog.com/static/:path*',
    },
    {
      source: '/relay-sdK3/:path*',
      destination: 'https://eu.i.posthog.com/:path*',
    },
  ];
},
// Required for PostHog trailing slash API requests
skipTrailingSlashRedirect: true,

The path /relay-sdK3 is deliberately non-obvious — using something like /posthog or /analytics is trivially easy for blockers to detect and block. An opaque path like this is much harder to fingerprint.

The skipTrailingSlashRedirect: true option is required because PostHog makes some requests with trailing slashes, and Next.js's default behaviour strips those before the rewrite matches.

Why EU hosting matters

If your users are in Europe, routing data to eu.i.posthog.com means their analytics data is stored in the EU and never leaves it. That is a meaningful GDPR consideration, and it is why Plainform uses the EU endpoint rather than the default US one.

Tracking Custom Events

Page views are captured automatically by PostHog analytics. For everything else — button clicks, form submissions, feature usage, purchases — you use posthog.capture.

In Client Components

The usePostHog hook gives you access to the PostHog instance inside any Client Component:

'use client';

import { usePostHog } from 'posthog-js/react';

export function PricingButton() {
  const posthog = usePostHog();

  const handleClick = () => {
    posthog.capture('pricing_cta_clicked', {
      plan: 'pro',
      page: window.location.pathname,
    });
  };

  return <button onClick={handleClick}>Get Started</button>;
}

The first argument to capture is the event name — use a consistent naming convention across your app (noun_verb works well). The second argument is a properties object. You can pass anything serializable: strings, numbers, booleans, nested objects.

Common events to track in a SaaS product

// Feature usage
posthog.capture('feature_used', { feature_name: 'export_csv' });

// Form submissions
posthog.capture('form_submitted', { form_name: 'contact', success: true });

// Upgrade intent
posthog.capture('upgrade_clicked', { current_plan: 'free', target_plan: 'pro' });

// Purchase completed
posthog.capture('purchase_completed', { amount: 99, plan: 'pro', currency: 'usd' });

// Error encountered
posthog.capture('error_encountered', { error_type: 'payment_failed', page: '/checkout' });

Tracking docs feedback

Plainform uses PostHog analytics directly in the docs feedback component to capture how users rate documentation pages. It also calls posthog.identify before capturing the event, so the feedback is tied to the logged-in user:

import posthog from 'posthog-js';

function onSubmit() {
  const feedback = {
    opinion,  // 'good' | 'bad'
    message,
    url,
  };

  posthog.identify(user?.primaryEmailAddress?.emailAddress || undefined);
  posthog.capture('on_rate_docs', feedback);
}

This means every piece of docs feedback in PostHog shows which user submitted it, what page they were on, and what they said — without building any custom backend infrastructure.

Identifying Users

Anonymous events are useful for measuring traffic. But in a SaaS product, you usually want to know which specific user did what, and what plan they are on.

Calling posthog.identify

After a user signs in, call posthog.identify with their unique ID and any traits you want attached to them in PostHog:

'use client';

import { usePostHog } from 'posthog-js/react';
import { useUser } from '@clerk/nextjs';
import { useEffect } from 'react';

export function UserIdentifier() {
  const posthog = usePostHog();
  const { user } = useUser();

  useEffect(() => {
    if (user) {
      posthog.identify(user.id, {
        email: user.emailAddresses[0]?.emailAddress,
        name: user.fullName,
        created_at: user.createdAt,
      });
    }
  }, [user, posthog]);

  return null;
}

Once identified, all subsequent events from that browser session are linked to that user in PostHog. You can look up a specific user in the People section and see their full event history.

Resetting identity on sign-out

When a user signs out, reset the PostHog identity so the next user on the same device starts a fresh anonymous session:

import posthog from 'posthog-js';

function handleSignOut() {
  posthog.reset();
  // then proceed with sign-out
}

Without this, if two different users share a device (a shared computer, for example), their events would be merged into one profile.

GDPR and Cookieless Mode

The cookieless_mode: 'on_reject' option in the initialization config tells PostHog analytics to avoid setting cookies unless the user has explicitly accepted. Until consent is given, PostHog operates without persistent identifiers.

This is the behaviour Plainform's cookie banner is built around. When a user accepts cookies, PostHog switches to full tracking mode with a persistent anonymous ID. When they reject, PostHog keeps running but stays cookieless — session data is not persisted across page loads.

What cookieless mode means in practice

In cookieless mode:

  • No cookies or localStorage entries are written for tracking
  • Sessions are not stitched together across page loads
  • Events are still captured and sent, but without a persistent user identifier
  • User identification via posthog.identify still works within a single session

For most SaaS products where you care most about signed-in user behaviour, this is a reasonable trade-off. The analytics you care about — what your paying users do — is captured even in cookieless mode, because you identify them when they sign in.

Viewing Your Data in PostHog

Once events are flowing, the PostHog dashboard gives you several ways to make sense of them.

Insights

Insights are queries over your event data. The most common ones for a SaaS product:

  • Trends: How many times was purchase_completed fired this week? How is that trending over time?
  • Funnels: Of users who viewed the pricing page, what percentage clicked the CTA, and what percentage completed checkout?
  • Retention: Of users who signed up in a given week, what percentage were still active 30 days later?

Session replays

PostHog can record sessions and let you replay exactly what a user did. This is invaluable for understanding confusing behaviour in your event data — if you see users dropping off at a specific step, you can watch a replay to see why.

Feature flags

Feature flags in PostHog analytics let you ship features to a percentage of users, to specific cohorts, or by user property. Because flags are evaluated on the same platform as your event data, you can immediately see how a flag change affects your metrics without switching tools.

How Plainform Uses PostHog

In Plainform, PostHog analytics is initialized once in instrumentation-client.ts, runs through a reverse proxy on /relay-sdK3, and operates in cookieless mode until the user accepts the cookie banner. User identification happens automatically after sign-in via the Clerk integration.

The only custom event captured by default is docs feedback via posthog.capture('on_rate_docs', feedback). Everything else — page views, clicks, navigation — is captured automatically.

When you build your product on Plainform, this infrastructure is already in place. You add posthog.capture calls wherever you want visibility into user behaviour, and the data flows into PostHog without any additional setup.

Summary

PostHog analytics in a Next.js SaaS app comes down to a few core pieces:

  • Initialize with instrumentation-client.ts to keep the root layout as a Server Component
  • Use a reverse proxy via Next.js rewrites to avoid ad blockers and keep EU data in the EU
  • Set cookieless_mode: 'on_reject' to handle GDPR without blocking all analytics
  • Use posthog.capture with consistent event names and meaningful properties
  • Call posthog.identify after sign-in to link events to real users
  • Reset with posthog.reset() on sign-out to avoid cross-user contamination

Get this right once and you have a solid foundation for understanding what your users actually do — not just how many of them showed up.

Comments on this page

Leave comment

Stay up to date with our latest product updates. Unsubscribe anytime!

2026 © All rights reserved