Skip to main content
Technology & EngineeringAnalytics Services249 lines

PostHog Analytics

"PostHog: product analytics, event tracking, feature flags, session recordings, A/B testing, self-hosted/cloud, Next.js SDK"

Quick Summary21 lines
PostHog is an open-source product analytics platform that combines event tracking, feature flags,
session recordings, A/B testing, and more into a single platform. It can be self-hosted or used
as a cloud service, giving teams full control over their data. The key principle is to instrument
events that represent meaningful user actions, use feature flags to gate rollouts safely, and

## Key Points

- **Disable automatic pageview capture** in Next.js and track manually via the router to avoid
- **Use `posthog.group()`** for B2B products to associate users with companies and analyze
- **Mask sensitive elements** using `data-ph-mask` attributes to prevent PII from appearing in
- **Evaluate feature flags server-side** for critical paths like access control to avoid flash
- **Shut down the server client** after capturing events in serverless functions to ensure the
- **Use flag payloads** to deliver configuration alongside feature flags, reducing the need for
- **Set `persistence: "localStorage+cookie"`** to maintain user identity across subdomains while
- **Tracking everything without a plan.** Autocapture is useful for discovery, but relying on it
- **Calling `posthog.init()` multiple times.** Guard initialization with a loaded check to prevent
- **Evaluating feature flags on every render.** Use the React hooks (`useFeatureFlagEnabled`) which
- **Ignoring server-side shutdown.** In serverless environments, failing to call `client.shutdown()`
- **Storing PII in event properties.** Avoid sending emails, names, or other personal data as event
skilldb get analytics-services-skills/PostHog AnalyticsFull skill: 249 lines
Paste into your CLAUDE.md or agent config

PostHog Analytics

Core Philosophy

PostHog is an open-source product analytics platform that combines event tracking, feature flags, session recordings, A/B testing, and more into a single platform. It can be self-hosted or used as a cloud service, giving teams full control over their data. The key principle is to instrument events that represent meaningful user actions, use feature flags to gate rollouts safely, and leverage session recordings plus analytics together to understand both the "what" and the "why" behind user behavior.

Setup

Installation

// Install the PostHog JavaScript SDK and Next.js integration
// npm install posthog-js posthog-node

// lib/posthog.ts — Client-side initialization
import posthog from "posthog-js";

export function initPostHog(): void {
  if (typeof window !== "undefined" && !posthog.__loaded) {
    posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
      api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST ?? "https://app.posthog.com",
      capture_pageview: false, // We handle this manually in Next.js
      capture_pageleave: true,
      persistence: "localStorage+cookie",
      autocapture: true,
      session_recording: {
        maskTextSelector: "[data-ph-mask]",
      },
    });
  }
}

export { posthog };

Next.js Provider Setup

// app/providers.tsx
"use client";

import { useEffect } from "react";
import { usePathname, useSearchParams } from "next/navigation";
import posthog from "posthog-js";
import { PostHogProvider as PHProvider } from "posthog-js/react";
import { initPostHog } from "@/lib/posthog";

function PostHogPageView(): null {
  const pathname = usePathname();
  const searchParams = useSearchParams();

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

  return null;
}

export function PostHogProvider({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    initPostHog();
  }, []);

  return (
    <PHProvider client={posthog}>
      <PostHogPageView />
      {children}
    </PHProvider>
  );
}

Server-Side Client

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

let posthogServerClient: PostHog | null = null;

export function getPostHogServer(): PostHog {
  if (!posthogServerClient) {
    posthogServerClient = new PostHog(process.env.POSTHOG_API_KEY!, {
      host: process.env.POSTHOG_HOST ?? "https://app.posthog.com",
      flushAt: 1,
      flushInterval: 0,
    });
  }
  return posthogServerClient;
}

Key Techniques

Event Tracking

// Track custom events with properties
import posthog from "posthog-js";

function trackPurchase(item: { id: string; name: string; price: number }): void {
  posthog.capture("purchase_completed", {
    item_id: item.id,
    item_name: item.name,
    price: item.price,
    currency: "USD",
  });
}

// Identify users after login
function identifyUser(user: { id: string; email: string; plan: string }): void {
  posthog.identify(user.id, {
    email: user.email,
    plan: user.plan,
    signed_up_at: new Date().toISOString(),
  });
}

// Set group-level properties (for B2B analytics)
function setOrganization(orgId: string, orgName: string): void {
  posthog.group("company", orgId, {
    name: orgName,
    industry: "technology",
  });
}

Feature Flags

// Client-side feature flag checks
import { useFeatureFlagEnabled, useFeatureFlagPayload } from "posthog-js/react";

function PricingPage(): JSX.Element {
  const showNewPricing = useFeatureFlagEnabled("new-pricing-page");
  const pricingConfig = useFeatureFlagPayload("new-pricing-page") as {
    tiers: Array<{ name: string; price: number }>;
  } | undefined;

  if (showNewPricing && pricingConfig) {
    return <NewPricingLayout tiers={pricingConfig.tiers} />;
  }
  return <LegacyPricingLayout />;
}

// Server-side feature flag evaluation
import { getPostHogServer } from "@/lib/posthog-server";

async function getFeatureFlag(userId: string, flagKey: string): Promise<boolean> {
  const client = getPostHogServer();
  const isEnabled = await client.isFeatureEnabled(flagKey, userId);
  return isEnabled ?? false;
}

A/B Testing and Experiments

// Using experiments with automatic exposure tracking
import { useFeatureFlagVariantKey } from "posthog-js/react";

function SignupButton(): JSX.Element {
  const variant = useFeatureFlagVariantKey("signup-cta-experiment");

  const buttonText: Record<string, string> = {
    control: "Sign Up",
    "variant-a": "Get Started Free",
    "variant-b": "Start Your Trial",
  };

  return (
    <button className="btn-primary">
      {buttonText[variant as string] ?? "Sign Up"}
    </button>
  );
}

Server-Side Event Capture

// Capture events from API routes or server actions
import { getPostHogServer } from "@/lib/posthog-server";

async function handleWebhook(event: WebhookEvent): Promise<void> {
  const client = getPostHogServer();

  client.capture({
    distinctId: event.userId,
    event: "subscription_renewed",
    properties: {
      plan: event.plan,
      amount: event.amount,
      renewal_date: event.date,
    },
  });

  await client.shutdown();
}

Best Practices

  • Disable automatic pageview capture in Next.js and track manually via the router to avoid duplicate events on client-side navigation.
  • Use posthog.group() for B2B products to associate users with companies and analyze behavior at the organization level.
  • Mask sensitive elements using data-ph-mask attributes to prevent PII from appearing in session recordings.
  • Evaluate feature flags server-side for critical paths like access control to avoid flash of incorrect content.
  • Shut down the server client after capturing events in serverless functions to ensure the event queue flushes before the function terminates.
  • Use flag payloads to deliver configuration alongside feature flags, reducing the need for additional API calls or config files.
  • Set persistence: "localStorage+cookie" to maintain user identity across subdomains while still supporting cross-domain tracking via cookies.

Anti-Patterns

  • Tracking everything without a plan. Autocapture is useful for discovery, but relying on it exclusively leads to noisy data. Define a tracking plan with named events for key actions.
  • Calling posthog.init() multiple times. Guard initialization with a loaded check to prevent duplicate instances and double-counted events.
  • Evaluating feature flags on every render. Use the React hooks (useFeatureFlagEnabled) which cache results, rather than calling posthog.isFeatureEnabled() directly in render logic.
  • Ignoring server-side shutdown. In serverless environments, failing to call client.shutdown() means events may be lost because the function terminates before the queue flushes.
  • Storing PII in event properties. Avoid sending emails, names, or other personal data as event properties. Use posthog.identify() to link user identity separately from event data.
  • Using feature flags without fallback values. Always provide a default behavior for when the flag evaluation fails or returns undefined, ensuring the application degrades gracefully.

Install this skill directly: skilldb add analytics-services-skills

Get CLI access →