Product Analytics with PostHog

22. November, 2025 11 min read Develop

All-in-One Analytics for Next.js

Understanding how users interact with your application is essential for building better products. PostHog is an open-source platform that combines product analytics, session replay, feature flags, A/B testing, and surveys into a single tool — replacing what would otherwise require five or more separate services.

With over 32,000 GitHub stars and a dedicated Next.js package, PostHog has become a go-to choice for developers who want powerful analytics without the vendor lock-in. In this post, we’ll set it up in a Next.js App Router project and explore its key features.

Why PostHog?

The traditional analytics stack for a modern web application looks something like this: Google Analytics or Mixpanel for product analytics, Hotjar for session recordings, LaunchDarkly for feature flags, Optimizely for A/B tests, and Typeform for surveys. That’s five vendors, five billing relationships, five SDKs, and five dashboards to check.

PostHog replaces all of them with a single platform. But what makes it particularly appealing for developers is its open-source nature. The core is MIT-licensed, meaning you can self-host it on your own infrastructure if data sovereignty is a concern. Even if you use their cloud offering, the code is transparent — you know exactly what’s being collected and how.

The pricing model is also developer-friendly. The free tier includes 1 million analytics events, 5,000 session recordings, 1 million feature flag requests, and 1,500 survey responses per month — more than enough for most projects starting out.

Setting Up in Next.js

PostHog provides a dedicated @posthog/next package that handles the complex parts of Next.js integration — identity synchronization between client and server, feature flag bootstrapping, and automatic event flushing.

1. Install the Package

npm install @posthog/next

2. Set Environment Variables

# .env.local
NEXT_PUBLIC_POSTHOG_KEY=phc_your_project_api_key
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com

3. Add the Provider

Wrap your application with the PostHogProvider in your root layout:

// app/layout.tsx
import { PostHogProvider, PostHogPageView } from '@posthog/next';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <PostHogProvider>
          <PostHogPageView />
          {children}
        </PostHogProvider>
      </body>
    </html>
  );
}

PostHogPageView handles automatic pageview tracking, including client-side navigations in the App Router. That’s the entire setup — PostHog is now tracking pageviews and autocapturing user interactions.

Autocapture

One of PostHog’s most practical features is autocapture. Without writing any tracking code, it automatically records:

  • Clicks and interactions on buttons, links, and form elements
  • Pageviews and page exits including navigation timing
  • Form submissions and input changes
  • Web Vitals (LCP, FID, CLS) for performance monitoring
  • Console errors and exceptions for error tracking
  • Scroll depth and heatmap data for understanding engagement

This means you get useful analytics data from day one, before writing a single custom event. You can fine-tune what’s captured through configuration:

<PostHogProvider
  clientOptions={{
    api_host: '/ingest',
    autocapture: {
      dom_event_allowlist: ['click', 'submit'],
      url_allowlist: ['https://yourdomain.com/app/*'],
      css_selector_allowlist: ['[data-track]'],
    },
  }}
>

For sensitive elements like credit card inputs, add the ph-no-capture class to exclude them entirely.

Custom Event Tracking

When autocapture isn’t enough, tracking custom events is straightforward:

'use client';

import posthog from 'posthog-js';

export function PricingCard({ plan }: { plan: string }) {
  const handleUpgrade = () => {
    posthog.capture('upgrade_clicked', {
      plan,
      source: 'pricing_page',
    });
  };

  return (
    <button onClick={handleUpgrade}>
      Upgrade to {plan}
    </button>
  );
}

Custom events accept arbitrary properties, letting you attach any context that’s useful for analysis. Events appear in the PostHog dashboard within minutes, where you can build trends, funnels, retention charts, and path analyses.

Identifying Users

To link anonymous sessions to known users, call identify after authentication:

'use client';

import posthog from 'posthog-js';
import { useEffect } from 'react';

export function AuthProvider({ user }: { user: { id: string; email: string } | null }) {
  useEffect(() => {
    if (user) {
      posthog.identify(user.id, {
        email: user.email,
      });
    }
  }, [user]);

  return null;
}

This merges the anonymous session with the identified user, preserving their pre-login activity. Call posthog.reset() on logout to clear the identity.

Feature Flags

Feature flags let you control feature rollouts without redeploying. Create a flag in the PostHog dashboard, then use it in your components:

'use client';

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

export function Dashboard() {
  const showNewDashboard = useFeatureFlagEnabled('new-dashboard');

  if (showNewDashboard) {
    return <NewDashboard />;
  }

  return <OldDashboard />;
}

PostHog supports several flag types:

  • Boolean: Simple on/off toggles for feature releases
  • Multivariate: Multiple variants with configurable rollout percentages
  • Remote config: Static configuration values without redeployment

Flags can be targeted by user properties, geographic location, percentage rollouts, cohorts, or even dependencies on other flags.

Server-Side Feature Flags

For Server Components, use the getPostHog helper:

import { getPostHog } from '@posthog/next';

export default async function Page() {
  const posthog = await getPostHog();
  const flags = await posthog.getAllFlags();

  return (
    <div>
      {flags['new-header'] ? <NewHeader /> : <OldHeader />}
    </div>
  );
}

To prevent flickering on the client, use the bootstrapFlags prop on PostHogProvider. This evaluates flags server-side and passes them to the client immediately, so hooks like useFeatureFlagEnabled return values on the first render without waiting for a network request.

Session Replay

Session replay records user sessions as DOM snapshots (not video), showing you exactly what users see and do:

<PostHogProvider
  clientOptions={{
    api_host: '/ingest',
    session_recording: {
      maskAllInputs: true,
      maskTextSelector: '.sensitive-data',
    },
  }}
>

Recordings capture mouse movements, clicks, scrolls, network requests, and console logs. Privacy controls run in the browser — sensitive data is masked before it ever leaves the client.

You can control when recording happens through several mechanisms:

  • URL-based triggers: Only record on specific pages
  • Event-based triggers: Start recording when errors occur
  • Feature flag integration: Record only for flagged users
  • Sampling: Record a percentage of sessions to manage costs

Session replays are particularly powerful when combined with other PostHog features. You can watch recordings of users who dropped off in a funnel, or replay sessions from a specific A/B test variant to understand why it performed differently.

A/B Testing

PostHog’s experimentation framework builds on feature flags to provide statistically rigorous A/B testing:

'use client';

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

export function CheckoutButton() {
  const variant = useFeatureFlagVariantKey('checkout-experiment');

  if (variant === 'test') {
    return <button className="btn-green">Complete Purchase</button>;
  }

  return <button className="btn-blue">Checkout</button>;
}

Create the experiment in the PostHog dashboard, define your success metrics (conversion rates, revenue, retention), and PostHog handles the statistical analysis. It uses Bayesian methodology by default, giving you direct probability statements like “96% chance variant B increases conversion” rather than abstract p-values.

Experiments support up to 9 test variants plus a control group, group-level testing (by company or team, not just individual users), and holdout groups for measuring long-term effects.

Surveys

Collect user feedback directly in your application with built-in surveys:

'use client';

import posthog from 'posthog-js';

export function FeedbackButton() {
  return (
    <button onClick={() => posthog.displaySurvey('survey-id', {
      displayType: 'Popover',
    })}>
      Give Feedback
    </button>
  );
}

PostHog offers popover surveys (built-in UI), API-driven surveys (your own UI), feedback buttons, and hosted survey links. Question types include free text, ratings (emoji or numeric), single choice, and multiple choice.

Surveys can be targeted by feature flag status, URL patterns, device type, user properties, or specific user events during a session — letting you ask the right questions to the right users at the right time.

Avoiding Ad Blockers

Analytics scripts are often blocked by ad blockers. PostHog’s Next.js package includes a built-in proxy option, but you can also set up a reverse proxy through Next.js rewrites:

// next.config.js
const nextConfig = {
  async rewrites() {
    return [
      {
        source: '/ingest/static/:path*',
        destination: 'https://us-assets.i.posthog.com/static/:path*',
      },
      {
        source: '/ingest/:path*',
        destination: 'https://us.i.posthog.com/:path*',
      },
    ];
  },
};

Then set api_host: '/ingest' in your PostHog configuration. Requests now route through your domain, bypassing most ad blockers while keeping your analytics accurate.

Server-Side Tracking

PostHog isn’t limited to client-side analytics. You can track events from Server Components, Server Actions, and Route Handlers using the server-side helper:

import { getPostHog } from '@posthog/next';

export async function purchaseAction(formData: FormData) {
  'use server';

  const posthog = await getPostHog();
  const plan = formData.get('plan') as string;

  // Process the purchase...
  await processPurchase(plan);

  // Track server-side
  posthog.capture({
    event: 'purchase_completed',
    properties: { plan, source: 'upgrade_page' },
  });
}

The @posthog/next package automatically synchronizes user identity between client and server through middleware cookies. This means server-side events are attributed to the same user as client-side events — no manual identity management needed.

On Vercel, PostHog auto-detects waitUntil from @vercel/functions to flush events in the background without blocking responses. For other hosting platforms, you can pass a custom waitUntil function through the serverOptions prop on PostHogProvider.

Building Insights

Once data is flowing, PostHog provides several analysis tools in its dashboard:

  • Trends: Track event volume over time — how many users clicked “Upgrade” this week vs. last week?
  • Funnels: Measure conversion through multi-step processes — what percentage of users who view pricing actually complete a purchase?
  • Retention: Understand return behavior — are users coming back after their first week?
  • Paths: Visualize navigation flows — what do users do after landing on the homepage?
  • Lifecycle: Segment users into new, returning, resurrecting, and dormant categories
  • Stickiness: Measure engagement frequency — how often do active users return?

All of these can be filtered by user properties, feature flag status, geographic location, and any custom properties you’ve attached to events. You can also write raw SQL queries through PostHog’s HogQL interface for analysis that goes beyond the built-in visualizations.

Insights can be combined into dashboards and shared with your team. This makes PostHog not just a developer tool but a product-wide analytics platform that designers, product managers, and stakeholders can all use.

PostHog vs Alternatives

PostHog Mixpanel Google Analytics
Open-source Yes (MIT) No No
Self-hostable Yes No No
All-in-one Analytics + Replay + Flags + A/B + Surveys Analytics focused Analytics only
Free tier 1M events + 5K recordings + 1M flag requests 20M events (analytics only) Free but limited
Data ownership Full control Vendor-hosted Google-hosted
Developer experience SDK-first, code-level Dashboard-first Tag-based
Privacy/GDPR EU hosting, self-host, cookieless Limited options Complex

PostHog’s main advantage is breadth — you get the entire product analytics toolkit from a single vendor. The main trade-off is that each individual feature may not be as deep as a dedicated tool. LaunchDarkly has more advanced flag targeting, Hotjar has more mature heatmaps, and Mixpanel has deeper cohort analysis. But for most teams, PostHog’s integrated approach eliminates the overhead of managing multiple tools and the friction of correlating data across them.

Privacy and Self-Hosting

PostHog takes privacy seriously with layered controls:

  • Data residency: Choose between EU and US cloud hosting
  • Cookieless mode: Use in-memory storage instead of browser cookies
  • IP anonymization: Disable IP capture entirely (automatic for EU organizations)
  • Client-side filtering: Modify or reject events before they’re sent
  • User opt-out: Respect user preferences at initialization or per-person level

For maximum control, self-hosting is available with a one-line deployment:

curl -fsSL https://raw.githubusercontent.com/posthog/posthog/HEAD/bin/deploy-hobby | bash

This deploys the full PostHog stack on a Linux VM (4 vCPU, 16GB RAM minimum) with automatic SSL via Let’s Encrypt. You own all the data with no external dependencies.

Conclusion

PostHog fills a critical gap in the modern Next.js stack by consolidating analytics, session replay, feature flags, experimentation, and surveys into a single platform. Its open-source nature, generous free tier, and dedicated Next.js package make it an excellent choice for teams that want comprehensive product insights without the complexity of managing multiple vendor relationships.

The integration is minimal — a single package, a provider component, and you’re tracking. From there, the platform grows with your needs: add feature flags when you’re ready for controlled rollouts, enable session replay when you need to debug user issues, and set up experiments when you want data-driven decisions.

‘Till next time!