Skip to main content

How to Add Product Analytics with PostHog

·APIScout Team
posthogproduct analyticsfeature flagstutorialapi integration

How to Add Product Analytics with PostHog

PostHog is the open-source product analytics platform. One tool replaces five: analytics, session replay, feature flags, A/B testing, and surveys. Self-host it or use their cloud — either way, you own your data.

What You'll Build

  • Event tracking (pageviews, clicks, custom events)
  • Feature flags (gradual rollouts, A/B tests)
  • Session replay (watch real user sessions)
  • Funnel analysis (signup → activation → conversion)
  • User identification and properties

Prerequisites: React/Next.js app, PostHog account (free: 1M events/month, 5K sessions).

1. Setup

Install

npm install posthog-js posthog-node

Initialize (Client-Side)

// lib/posthog.ts
import posthog from 'posthog-js';

export function initPostHog() {
  if (typeof window !== 'undefined') {
    posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
      api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://us.i.posthog.com',
      person_profiles: 'identified_only',
      capture_pageview: false, // We'll handle this manually
      capture_pageleave: true,
    });
  }
  return posthog;
}

PostHog Provider (Next.js)

// app/providers.tsx
'use client';
import posthog from 'posthog-js';
import { PostHogProvider } from 'posthog-js/react';
import { useEffect } from 'react';

export function PHProvider({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
      api_host: '/ingest', // Proxy through your domain (avoids ad blockers)
      person_profiles: 'identified_only',
    });
  }, []);

  return <PostHogProvider client={posthog}>{children}</PostHogProvider>;
}

Add to Layout

// app/layout.tsx
import { PHProvider } from './providers';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <PHProvider>{children}</PHProvider>
      </body>
    </html>
  );
}

Proxy Configuration (Avoid Ad Blockers)

// next.config.js
module.exports = {
  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*',
      },
    ];
  },
};

2. Event Tracking

Automatic Pageviews

// components/PostHogPageview.tsx
'use client';
import { usePathname, useSearchParams } from 'next/navigation';
import { usePostHog } from 'posthog-js/react';
import { useEffect } from 'react';

export function PostHogPageview() {
  const pathname = usePathname();
  const searchParams = useSearchParams();
  const posthog = usePostHog();

  useEffect(() => {
    if (pathname && posthog) {
      let url = window.origin + pathname;
      if (searchParams.toString()) {
        url += `?${searchParams.toString()}`;
      }
      posthog.capture('$pageview', { $current_url: url });
    }
  }, [pathname, searchParams, posthog]);

  return null;
}

Custom Events

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

function PricingPage() {
  const posthog = usePostHog();

  const handlePlanSelect = (plan: string) => {
    posthog.capture('plan_selected', {
      plan_name: plan,
      plan_price: getPlanPrice(plan),
      source: 'pricing_page',
    });
  };

  const handleCTAClick = () => {
    posthog.capture('cta_clicked', {
      cta_text: 'Start Free Trial',
      page: 'pricing',
    });
  };

  return (
    <div>
      <button onClick={() => handlePlanSelect('pro')}>Choose Pro</button>
      <button onClick={handleCTAClick}>Start Free Trial</button>
    </div>
  );
}

User Identification

// After login
posthog.identify(user.id, {
  email: user.email,
  name: user.name,
  plan: user.plan,
  company: user.company,
});

// After logout
posthog.reset();

Server-Side Tracking

// lib/posthog-server.ts
import { PostHog } from 'posthog-node';

const posthogServer = new PostHog(process.env.POSTHOG_KEY!, {
  host: 'https://us.i.posthog.com',
});

// Track server-side events
posthogServer.capture({
  distinctId: userId,
  event: 'subscription_created',
  properties: {
    plan: 'pro',
    amount: 2900,
    currency: 'usd',
    billing_period: 'monthly',
  },
});

// Flush before process exits
await posthogServer.shutdown();

3. Feature Flags

Create a Flag

In PostHog Dashboard → Feature Flags → New Feature Flag:

  • Key: new-pricing-page
  • Rollout: 50% of users (or specific conditions)

Check Flag (Client)

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

function PricingPage() {
  const newPricing = useFeatureFlagEnabled('new-pricing-page');

  if (newPricing) {
    return <NewPricingPage />;
  }
  return <OldPricingPage />;
}

Check Flag (Server)

// In API route or server component
import { posthogServer } from '@/lib/posthog-server';

const isEnabled = await posthogServer.isFeatureEnabled(
  'new-pricing-page',
  userId
);

Flag with Payload

// PostHog dashboard: set payload as JSON
const flagPayload = posthog.getFeatureFlagPayload('pricing-experiment');
// Returns: { discount: 20, cta_text: "Save 20% Today" }

4. Session Replay

Enable Recording

posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
  api_host: '/ingest',
  session_recording: {
    maskAllInputs: true, // Privacy: mask form inputs
    maskTextSelector: '.sensitive', // Mask specific elements
  },
});

Control Recording

// Start recording manually
posthog.startSessionRecording();

// Stop recording
posthog.stopSessionRecording();

// Only record for specific users
if (user.plan === 'enterprise') {
  posthog.startSessionRecording();
}

5. Funnels and Insights

Define Events for Funnel

Track these events to build a conversion funnel:

// Step 1: Visit pricing page
posthog.capture('pricing_page_viewed');

// Step 2: Select a plan
posthog.capture('plan_selected', { plan: 'pro' });

// Step 3: Enter payment info
posthog.capture('payment_info_entered');

// Step 4: Complete purchase
posthog.capture('purchase_completed', { plan: 'pro', amount: 29 });

In PostHog Dashboard → Insights → New Insight → Funnel:

  • Step 1: pricing_page_viewed
  • Step 2: plan_selected
  • Step 3: payment_info_entered
  • Step 4: purchase_completed

Key Insights to Set Up

Insight TypeConfigurationBusiness Value
FunnelSignup → Activation → ConversionConversion optimization
Retention"Login" event, weekly cohortsStickiness measurement
TrendsDaily active users, feature usageGrowth tracking
PathsUser navigation patternsUX optimization
StickinessHow many days/week users returnEngagement depth

6. Group Analytics

Track company-level metrics (B2B):

// Associate user with a company
posthog.group('company', 'company_123', {
  name: 'Acme Corp',
  plan: 'enterprise',
  employee_count: 500,
});

// Events are now attributed to both user and company
posthog.capture('feature_used', { feature: 'api_dashboard' });

Self-Hosted vs Cloud

FactorCloudSelf-Hosted
SetupInstantDocker Compose or Kubernetes
MaintenancePostHog handles itYou manage updates
Data locationUS/EUYour infrastructure
PricingUsage-basedFree (infrastructure costs only)
ScaleUnlimitedDepends on your infra
Best forMost teamsStrict data residency requirements

Self-Hosted Quick Start

# Docker Compose (recommended for small teams)
git clone https://github.com/PostHog/posthog.git
cd posthog
docker compose -f docker-compose.hobby.yml up -d

Pricing (Cloud)

FeatureFreePaid
Events1M/month$0.00031/event after 1M
Session replay5,000/month$0.005/recording after 5K
Feature flags1M requests$0.0001/request after 1M
Surveys250 responses$0.002/response after 250

Common Mistakes

MistakeImpactFix
Tracking everythingNoise, higher costsTrack 15-20 key events max
Not identifying usersCan't connect pre/post-login behaviorCall identify() after auth
Client-only trackingMissed server eventsUse posthog-node for server events
Not using proxyAd blockers block tracking (30-40%)Proxy through your domain
No event naming conventionMessy data, hard to queryUse snake_case consistently
Recording everythingPrivacy concerns, storage costsOnly record opt-in or specific flows

Choosing analytics? Compare PostHog vs Mixpanel vs Amplitude on APIScout — open-source vs proprietary, features, and pricing.

Comments