Claude Skill

api-analytics-setup-posthog

PostHog analytics and feature flags setup

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download agents-inc-skills-dist_plugins_api-analytics-setup-posthog_skills_api-analytics-setup-posthog-3a51ef5.zip · 8 KB
Part of agents-inc/skills — 130 skills

Install

skills CLI npx skills add https://github.com/agents-inc/skills/tree/main/dist/plugins/api-analytics-setup-posthog/skills/api-analytics-setup-posthog
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install agents-inc-skills@llmmart
Git git clone https://github.com/agents-inc/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole agents-inc/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

PostHog Analytics & Feature Flags Setup

Quick Guide: One-time setup for PostHog analytics and feature flags. Covers posthog-js client provider, posthog-node server client, and environment variables. PostHog handles both analytics AND feature flags with a generous free tier (1M events + 1M flag requests/month).


<critical_requirements>

CRITICAL: Before Using This Skill

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST initialize posthog-js only in a client/browser context - it requires browser APIs like window and localStorage)

(You MUST call posthog.shutdown(), posthog.flush(), or use captureImmediate() after server-side event capture to prevent lost events)

(You MUST use defaults: '2026-01-30' for automatic SPA page tracking and latest recommended behaviors)

</critical_requirements>


Auto-detection: PostHog setup, posthog-js, posthog-node, PostHogProvider, analytics setup, feature flags setup, event tracking setup, posthog.init

When to use:

  • Initial PostHog setup in a project
  • Configuring PostHogProvider for client-side analytics
  • Setting up posthog-node for server-side/API route event capture
  • Configuring environment variables for PostHog

When NOT to use:

  • Event tracking patterns after setup (use analytics event tracking skill)
  • Feature flag usage patterns (use feature flags skill)
  • Complex multi-environment setups with separate staging/production projects

Key patterns covered:

  • Client-side setup with PostHogProvider or framework initialization hook
  • Server-side setup with posthog-node
  • Environment variables (client vs server prefix)
  • User identification and reset flows
  • Serverless flush patterns (captureImmediate vs flush)

Detailed Resources:




<red_flags>

RED FLAGS

  • Initializing posthog-js on the server (requires browser APIs - will crash)
  • No flush() or captureImmediate() after server-side capture in serverless environments (events silently lost)
  • Client-side env vars not exposed to the browser bundle (check your framework's prefix convention)
  • Hardcoding API keys in source code instead of environment variables
  • Missing posthog.reset() on sign out (user identity bleeds to next session)
  • Not using defaults date option (manual pageview tracking required, misses recommended behaviors)
  • Not calling posthog.identify() after authentication (anonymous and authenticated sessions remain unlinked)
  • No person_profiles: 'identified_only' option (unnecessary anonymous profiles created, higher costs)
  • Not wrapping app with PostHogProvider when using hooks (hooks return null)
  • Forgetting to add environment variables to deployment platform (events fail silently)
  • Using different PostHog projects for dev/prod without realizing (separate data)

Gotchas & Edge Cases:

  • posthog-js must be initialized after window is available (hence useEffect or a client-side initialization hook)
  • Server-side SDK does NOT auto-flush like the client - you must explicitly call flush(), shutdown(), or use captureImmediate()
  • captureImmediate() is simpler for serverless but sends one HTTP request per event (no batching)
  • Free tier resets monthly (1M events then stops capturing until next month)
  • person_profiles: 'identified_only' reduces costs but means no anonymous user profiles are created
  • When using auto-initialization hooks, config values remain fixed for the session - bootstrapping only works if flags are evaluated on the server before render

</red_flags>


<critical_reminders>

CRITICAL REMINDERS

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST initialize posthog-js only in a client/browser context - it requires browser APIs like window and localStorage)

(You MUST call posthog.shutdown(), posthog.flush(), or use captureImmediate() after server-side event capture to prevent lost events)

(You MUST use defaults: '2026-01-30' for automatic SPA page tracking and latest recommended behaviors)

Failure to follow these rules will cause lost analytics events, broken tracking, or security vulnerabilities.

</critical_reminders>


Sources

Files (skills)
  • examples
    • core.md 5.9 KB
      # PostHog Setup - Core Examples
      
      > Essential patterns for PostHog setup. See [SKILL.md](../SKILL.md) for core concepts and [reference.md](../reference.md) for decision frameworks.
      >
      > **Related examples:** [server.md](server.md)
      
      ---
      
      ## Pattern 1: Client-Side Initialization (Standalone)
      
      Initialize posthog-js in a client-side entry point. This is the simplest approach when your framework supports a client initialization hook (e.g., an instrumentation or bootstrap file that runs only in the browser).
      
      ```typescript
      // ✅ Good Example - Client-side init in entry point
      import posthog from "posthog-js";
      
      const POSTHOG_DEFAULTS_VERSION = "2026-01-30";
      
      posthog.init(process.env.POSTHOG_KEY, {
        api_host: process.env.POSTHOG_HOST,
        defaults: POSTHOG_DEFAULTS_VERSION,
        person_profiles: "identified_only",
      });
      
      if (process.env.NODE_ENV === "development") {
        posthog.debug();
      }
      ```
      
      **Why good:** Simplest setup, no provider component needed, `defaults` date enables all recommended behaviors for that snapshot, `person_profiles: "identified_only"` reduces costs
      
      **When to use:** When your framework provides a client-side initialization hook. For React apps needing context-based access via hooks, use the PostHogProvider pattern below.
      
      ---
      
      ## Pattern 2: PostHogProvider Component (React)
      
      Create a provider component for React apps that need hook-based access to the PostHog client.
      
      ```typescript
      // ✅ Good Example - PostHog Provider (React)
      // lib/posthog/provider.tsx
      "use client";
      
      import { useEffect } from "react";
      import posthog from "posthog-js";
      import { PostHogProvider as PHProvider } from "posthog-js/react";
      
      const POSTHOG_DEFAULTS_VERSION = "2026-01-30";
      
      interface PostHogProviderProps {
        children: React.ReactNode;
      }
      
      export function PostHogProvider({ children }: PostHogProviderProps) {
        useEffect(() => {
          if (typeof window !== "undefined" && !posthog.__loaded) {
            posthog.init(process.env.POSTHOG_KEY!, {
              api_host: process.env.POSTHOG_HOST!,
              defaults: POSTHOG_DEFAULTS_VERSION,
              person_profiles: "identified_only",
              loaded: (posthog) => {
                if (process.env.NODE_ENV === "development") {
                  posthog.debug();
                }
              },
            });
          }
        }, []);
      
        return <PHProvider client={posthog}>{children}</PHProvider>;
      }
      ```
      
      **Why good:** `defaults` date enables all recommended behaviors for that snapshot, `person_profiles: "identified_only"` reduces costs, `'use client'` ensures browser context, debug mode aids development
      
      ---
      
      ## Pattern 3: App-Level Provider Integration
      
      Wrap the app with PostHogProvider at the root of your component tree.
      
      ```typescript
      // ✅ Good Example - Root layout with PostHog
      import { PostHogProvider } from "./lib/posthog/provider";
      
      interface RootLayoutProps {
        children: React.ReactNode;
      }
      
      export function RootLayout({ children }: RootLayoutProps) {
        return (
          <html lang="en">
            <body>
              <PostHogProvider>{children}</PostHogProvider>
            </body>
          </html>
        );
      }
      ```
      
      **Why good:** PostHogProvider wraps entire app, provider handles client-only initialization, children passed through correctly
      
      ```typescript
      // ❌ Bad Example - Initializing posthog-js outside a client context
      import posthog from "posthog-js";
      
      // BAD: posthog-js requires browser APIs, fails on server
      posthog.init("phc_xxx", { api_host: "https://us.i.posthog.com" });
      
      export function RootLayout({ children }: { children: React.ReactNode }) {
        return (
          <html lang="en">
            <body>{children}</body>
          </html>
        );
      }
      ```
      
      **Why bad:** posthog-js uses browser APIs (localStorage, window), initializing in a server-rendered context crashes, no provider means hooks won't work
      
      ---
      
      ## Pattern 4: User Identification
      
      Identify users after authentication to link anonymous and authenticated sessions.
      
      ```typescript
      // ✅ Good Example - Identifying user after sign in
      import { useEffect } from "react";
      import { usePostHog } from "posthog-js/react";
      
      export function usePostHogIdentify(
        user: { id: string; email: string; name: string } | null,
      ) {
        const posthog = usePostHog();
      
        useEffect(() => {
          if (user) {
            posthog.identify(user.id, {
              email: user.email,
              name: user.name,
            });
          }
        }, [user, posthog]);
      }
      ```
      
      **Why good:** `identify()` links anonymous to authenticated sessions, user properties enable cohort analysis, decoupled from any specific auth library
      
      ---
      
      ## Pattern 5: Reset on Sign Out
      
      Clear user identity when signing out to prevent data bleed.
      
      ```typescript
      // ✅ Good Example - Reset PostHog on sign out
      import { usePostHog } from "posthog-js/react";
      
      export function useSignOut() {
        const posthog = usePostHog();
      
        return () => {
          // After your auth sign-out logic completes:
          posthog.reset();
        };
      }
      ```
      
      **Why good:** `reset()` clears identity on sign out preventing data bleed between users on shared devices
      
      ```typescript
      // ❌ Bad Example - Missing reset on sign out
      function handleSignOut() {
        // Sign out logic runs but no posthog.reset()
        // Next user inherits previous identity!
      }
      ```
      
      **Why bad:** User identity bleeds between sessions, corrupts analytics data
      
      ---
      
      ## Pattern 6: Environment Variables
      
      Client-side variables must be exposed to the browser bundle (check your framework's prefix convention). Server-side variables should NOT be exposed.
      
      ```bash
      # ✅ Good Example - .env.local
      # Client-side (exposed to browser bundle)
      # Use your framework's public prefix: NEXT_PUBLIC_, VITE_, EXPO_PUBLIC_, etc.
      POSTHOG_KEY=phc_your_project_api_key
      POSTHOG_HOST=https://us.i.posthog.com
      
      # Server-side only (API routes, never exposed to client)
      POSTHOG_API_KEY=phc_your_project_api_key
      ```
      
      **Why good:** Separate client and server env vars, keys never hardcoded in source
      
      ```bash
      # ❌ Bad Example - Hardcoded keys
      # API keys directly in source code instead of environment variables
      posthog.init("phc_hardcoded_key_123", { api_host: "..." });
      ```
      
      **Why bad:** Hardcoded keys are committed to version control, cannot be rotated, and differ between environments
      
      ---
      
    • deployment.md 3.9 KB
      # PostHog Setup - Deployment Examples
      
      > Environment configuration and deployment patterns for PostHog. See [SKILL.md](../SKILL.md) for core concepts and [reference.md](../reference.md) for decision frameworks.
      >
      > **Related examples:** [core.md](core.md) | [server.md](server.md)
      
      ---
      
      ## Pattern 1: Environment Variables Template
      
      Create `.env.example` for team onboarding with clear documentation.
      
      ```bash
      # ✅ Good Example - Comprehensive .env.example
      # apps/client-next/.env.example
      
      # ================================================================
      # PostHog Analytics & Feature Flags
      # ================================================================
      # Get your API key from: https://posthog.com -> Project Settings -> API Keys
      #
      # Host options:
      #   US Cloud: https://us.i.posthog.com
      #   EU Cloud: https://eu.i.posthog.com
      #   Self-hosted: https://your-posthog-instance.com
      
      # Client-side (embedded in browser bundle)
      NEXT_PUBLIC_POSTHOG_KEY=phc_your_project_api_key
      NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
      
      # Server-side (API routes, never exposed to client)
      POSTHOG_API_KEY=phc_your_project_api_key
      POSTHOG_HOST=https://us.i.posthog.com
      ```
      
      **Why good:** Comments explain where to get keys, host options documented, clear separation between client and server variables
      
      ---
      
      ## Pattern 2: Vercel Deployment Configuration
      
      Configure PostHog environment variables for production deployment.
      
      ### Vercel Environment Variables
      
      | Variable                   | Environment                      | Value                      |
      | -------------------------- | -------------------------------- | -------------------------- |
      | `NEXT_PUBLIC_POSTHOG_KEY`  | Production, Preview, Development | `phc_xxx`                  |
      | `NEXT_PUBLIC_POSTHOG_HOST` | Production, Preview, Development | `https://us.i.posthog.com` |
      | `POSTHOG_API_KEY`          | Production, Preview, Development | `phc_xxx`                  |
      | `POSTHOG_HOST`             | Production, Preview, Development | `https://us.i.posthog.com` |
      
      **Why good:** Same project for dev/prod simplifies setup, Vercel handles env var injection at build time
      
      ### Local Development Override
      
      ```bash
      # apps/client-next/.env.local (gitignored)
      
      # Use the same keys for development
      NEXT_PUBLIC_POSTHOG_KEY=phc_your_project_api_key
      NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
      POSTHOG_API_KEY=phc_your_project_api_key
      POSTHOG_HOST=https://us.i.posthog.com
      ```
      
      **Why good:** `.env.local` is gitignored, allows local overrides, matches production config for consistency
      
      ---
      
      ## Pattern 3: Initial Dashboard Setup
      
      Configure PostHog dashboards after setup completion.
      
      ### Recommended Initial Dashboards
      
      1. **Web Analytics Dashboard** (built-in)
         - Page views, unique visitors, bounce rate
         - Traffic sources, referrers
         - Geographic distribution
      
      2. **Product Analytics Dashboard** (create)
         - Key conversion funnels
         - Feature adoption rates
         - Retention cohorts
      
      3. **Error Tracking** (enable plugin)
         - Error rate trends
         - Most common errors
         - Affected users
      
      ---
      
      ## Pattern 4: Initial Configuration Checklist
      
      Use this checklist to verify complete PostHog setup.
      
      ```markdown
      ## PostHog Setup Checklist
      
      ### Account Setup
      
      - [ ] Created PostHog account and organization
      - [ ] Created project for app
      - [ ] Copied API key to .env.local
      
      ### Client-Side Setup
      
      - [ ] Installed posthog-js (client)
      - [ ] Created PostHogProvider component
      - [ ] Wrapped app in provider (layout.tsx)
      
      ### Server-Side Setup
      
      - [ ] Installed posthog-node (server)
      - [ ] Created server client singleton
      - [ ] Added flush() to API routes
      
      ### Verification
      
      - [ ] Verified events appearing in PostHog dashboard
      - [ ] Tested in development mode (debug enabled)
      
      ### Deployment
      
      - [ ] Set up Vercel environment variables
      - [ ] Created .env.example for team
      - [ ] Verified events in production
      ```
      
      **Why good:** Step-by-step verification prevents missed configuration, covers all critical paths, easy to share with team
      
      ---
      
    • server.md 3.7 KB
      # PostHog Setup - Server-Side Examples
      
      > Server-side patterns for PostHog event capture. See [SKILL.md](../SKILL.md) for core concepts and [reference.md](../reference.md) for decision frameworks.
      >
      > **Related examples:** [core.md](core.md)
      
      ---
      
      ## Pattern 1: Server Client Singleton
      
      Create a singleton for server-side event capture to prevent multiple client instances.
      
      ```typescript
      // ✅ Good Example - PostHog server client singleton
      // lib/posthog/server.ts
      import { PostHog } from "posthog-node";
      
      const FLUSH_INTERVAL_MS = 10000;
      const FLUSH_AT_COUNT = 20;
      
      let posthogClient: PostHog | null = null;
      
      export function getPostHogServerClient(): PostHog {
        if (!posthogClient) {
          posthogClient = new PostHog(process.env.POSTHOG_API_KEY!, {
            host: process.env.POSTHOG_HOST || "https://us.i.posthog.com",
            flushInterval: FLUSH_INTERVAL_MS,
            flushAt: FLUSH_AT_COUNT,
          });
        }
        return posthogClient;
      }
      
      // For graceful shutdown in serverless environments
      export async function shutdownPostHog(): Promise<void> {
        if (posthogClient) {
          await posthogClient.shutdown();
          posthogClient = null;
        }
      }
      ```
      
      **Why good:** Singleton prevents multiple client instances, flushInterval/flushAt configure batching, shutdown function for graceful cleanup, works in serverless environments
      
      ---
      
      ## Pattern 2: API Route Usage with flush()
      
      Capture events in API routes with proper flush handling for serverless environments.
      
      ```typescript
      // ✅ Good Example - Capturing events in an API route
      import { getPostHogServerClient } from "./lib/posthog/server";
      
      export async function handleSignup(request: Request) {
        const posthog = getPostHogServerClient();
        const body = await request.json();
      
        // Capture signup event
        posthog.capture({
          distinctId: body.email,
          event: "user_signed_up",
          properties: {
            plan: body.plan,
            source: body.source,
          },
        });
      
        // CRITICAL: Flush events before response in serverless
        await posthog.flush();
      
        return Response.json({ success: true });
      }
      ```
      
      **Why good:** `flush()` ensures events are sent before function terminates (important for serverless), distinctId uses user identifier, properties add context
      
      ---
      
      ## Pattern 2b: Alternative - captureImmediate for Serverless
      
      For simpler serverless usage, use `captureImmediate` which awaits the HTTP request directly.
      
      ```typescript
      // ✅ Good Example - Using captureImmediate (recommended for serverless)
      import { getPostHogServerClient } from "./lib/posthog/server";
      
      export async function handleSignup(request: Request) {
        const posthog = getPostHogServerClient();
        const body = await request.json();
      
        // captureImmediate awaits the HTTP request - no flush needed
        await posthog.captureImmediate({
          distinctId: body.email,
          event: "user_signed_up",
          properties: {
            plan: body.plan,
            source: body.source,
          },
        });
      
        return Response.json({ success: true });
      }
      ```
      
      **Why good:** `captureImmediate` guarantees the HTTP request finishes before the function continues or shuts down, simpler than managing flush() calls, recommended for serverless by PostHog
      
      ---
      
      ## Pattern 3: Server-Side Anti-Pattern - Missing Flush
      
      ```typescript
      // ❌ Bad Example - Not flushing events in serverless
      import { getPostHogServerClient } from "./lib/posthog/server";
      
      export async function handleAction(request: Request) {
        const posthog = getPostHogServerClient();
      
        posthog.capture({
          distinctId: "user-123",
          event: "action_performed",
        });
      
        // BAD: No flush() - events may be lost in serverless!
        return Response.json({ success: true });
      }
      ```
      
      **Why bad:** PostHog batches events by default, serverless function may terminate before batch is sent, events are silently lost. Use `await posthog.flush()` or `captureImmediate()` instead.
      
      ---
      
  • reference.md 1.4 KB
    # PostHog Setup - Reference Guide
    
    > Decision frameworks for PostHog analytics and feature flags setup. See [SKILL.md](SKILL.md) for red flags and gotchas.
    
    ---
    
    ## Decision Framework
    
    ### PostHog Project Structure
    
    ```
    Single app or tight monorepo?
    ├─ YES → One PostHog project for all apps
    │   └─ Use custom properties to filter (app: "web", app: "admin")
    └─ NO → Multiple distinct products?
        └─ Separate projects per product
            └─ Still use ONE organization (pools billing)
    ```
    
    ### Client vs Server SDK
    
    ```
    Where is the event triggered?
    ├─ Browser/React component → posthog-js (usePostHog hook)
    ├─ API route/server action → posthog-node (getPostHogServerClient)
    │   └─ Serverless environment?
    │       ├─ YES → Use captureImmediate() (simplest)
    │       └─ Or → Use capture() + await flush()
    ├─ Server-rendered component → posthog-node (but consider if needed)
    └─ API middleware → posthog-node, flush after response
    ```
    
    ### US vs EU Hosting
    
    ```
    Where are your users?
    ├─ Primarily US/Americas → https://us.i.posthog.com
    ├─ Primarily EU/GDPR concerns → https://eu.i.posthog.com
    └─ Self-hosting required → Your own PostHog instance URL
    ```
    
    ---
    
    > **Code examples:** See [examples/core.md](examples/core.md) and [examples/server.md](examples/server.md) for full good/bad comparisons.
    
  • SKILL.md 7.5 KB
    ---
    name: api-analytics-setup-posthog
    description: PostHog analytics and feature flags setup
    ---
    
    # PostHog Analytics & Feature Flags Setup
    
    > **Quick Guide:** One-time setup for PostHog analytics and feature flags. Covers `posthog-js` client provider, `posthog-node` server client, and environment variables. PostHog handles both analytics AND feature flags with a generous free tier (1M events + 1M flag requests/month).
    
    ---
    
    <critical_requirements>
    
    ## CRITICAL: Before Using This Skill
    
    > **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)
    
    **(You MUST initialize posthog-js only in a client/browser context - it requires browser APIs like window and localStorage)**
    
    **(You MUST call `posthog.shutdown()`, `posthog.flush()`, or use `captureImmediate()` after server-side event capture to prevent lost events)**
    
    **(You MUST use `defaults: '2026-01-30'` for automatic SPA page tracking and latest recommended behaviors)**
    
    </critical_requirements>
    
    ---
    
    **Auto-detection:** PostHog setup, posthog-js, posthog-node, PostHogProvider, analytics setup, feature flags setup, event tracking setup, posthog.init
    
    **When to use:**
    
    - Initial PostHog setup in a project
    - Configuring PostHogProvider for client-side analytics
    - Setting up posthog-node for server-side/API route event capture
    - Configuring environment variables for PostHog
    
    **When NOT to use:**
    
    - Event tracking patterns after setup (use analytics event tracking skill)
    - Feature flag usage patterns (use feature flags skill)
    - Complex multi-environment setups with separate staging/production projects
    
    **Key patterns covered:**
    
    - Client-side setup with PostHogProvider or framework initialization hook
    - Server-side setup with posthog-node
    - Environment variables (client vs server prefix)
    - User identification and reset flows
    - Serverless flush patterns (captureImmediate vs flush)
    
    **Detailed Resources:**
    
    - [examples/core.md](examples/core.md) - Provider setup, layout integration, user identification, env vars
    - [examples/server.md](examples/server.md) - Server client singleton, API routes, serverless patterns
    - [reference.md](reference.md) - Decision frameworks
    
    ---
    
    <philosophy>
    
    ## Philosophy
    
    PostHog is a **product analytics + feature flags platform** that consolidates multiple tools into one. It's open-source, can be self-hosted, and has a generous free tier. For solo developers and small teams, PostHog eliminates the need for separate analytics and feature flag services.
    
    **Core principles:**
    
    1. **One platform for analytics + feature flags** - Reduces tool sprawl and cost
    2. **Usage-based pricing** - Pay for what you use, not per-project
    3. **Autocapture by default** - Automatic event tracking reduces manual instrumentation
    4. **Server and client SDKs** - Full coverage for SSR and client-side apps
    
    **When to use PostHog:**
    
    - Need both analytics and feature flags in one platform
    - Want generous free tier (1M events + 1M flag requests/month)
    - Prefer open-source with self-host option
    - Building product analytics (funnels, retention, sessions)
    
    **When NOT to use PostHog:**
    
    - Need advanced A/B testing with statistical rigor
    - Require real-time event streaming
    - Already have established analytics + flag tools
    
    </philosophy>
    
    ---
    
    <patterns>
    
    ## Core Patterns
    
    ### Pattern 1: PostHog Project Structure
    
    Use a single PostHog organization for your apps. One org pools billing. Use separate projects per app, or one project with custom properties to filter.
    
    ```
    PostHog Organization: "Your Company"
    ├── Project: "Main App" (or separate per app)
    │   ├── API Key: phc_xxx
    │   └── Host: https://us.i.posthog.com (or eu.i.posthog.com)
    ```
    
    **Why good:** Single org pools billing across all projects, usage-based pricing, 6 projects included on paid tier
    
    ---
    
    ### Pattern 2: Client-Side Setup
    
    Install `posthog-js` and configure a provider or use your framework's client-side initialization hook.
    
    Key config options: `defaults: "2026-01-30"` enables recommended behaviors, `person_profiles: "identified_only"` reduces costs.
    
    See [examples/core.md](examples/core.md) for full implementation of both approaches.
    
    **Why good:** `defaults` date enables automatic SPA page/leave tracking, `person_profiles: "identified_only"` reduces event costs, debug mode in development aids troubleshooting
    
    ---
    
    ### Pattern 3: Server-Side Setup with posthog-node
    
    Install `posthog-node` and create a singleton for server-side event capture.
    
    **Serverless flush options:**
    
    - `captureImmediate()` - simplest, awaits HTTP request directly (one request per event)
    - `capture()` + `await flush()` - batched, requires explicit flush before response returns
    
    See [examples/server.md](examples/server.md) for singleton setup, API route usage, and the flush anti-pattern.
    
    **Why good:** Singleton prevents multiple client instances, flushInterval/flushAt configure batching, captureImmediate simplifies serverless usage
    
    </patterns>
    
    ---
    
    <red_flags>
    
    ## RED FLAGS
    
    - Initializing posthog-js on the server (requires browser APIs - will crash)
    - No `flush()` or `captureImmediate()` after server-side capture in serverless environments (events silently lost)
    - Client-side env vars not exposed to the browser bundle (check your framework's prefix convention)
    - Hardcoding API keys in source code instead of environment variables
    - Missing `posthog.reset()` on sign out (user identity bleeds to next session)
    - Not using `defaults` date option (manual pageview tracking required, misses recommended behaviors)
    - Not calling `posthog.identify()` after authentication (anonymous and authenticated sessions remain unlinked)
    - No `person_profiles: 'identified_only'` option (unnecessary anonymous profiles created, higher costs)
    - Not wrapping app with PostHogProvider when using hooks (hooks return null)
    - Forgetting to add environment variables to deployment platform (events fail silently)
    - Using different PostHog projects for dev/prod without realizing (separate data)
    
    **Gotchas & Edge Cases:**
    
    - `posthog-js` must be initialized after `window` is available (hence useEffect or a client-side initialization hook)
    - Server-side SDK does NOT auto-flush like the client - you must explicitly call `flush()`, `shutdown()`, or use `captureImmediate()`
    - `captureImmediate()` is simpler for serverless but sends one HTTP request per event (no batching)
    - Free tier resets monthly (1M events then stops capturing until next month)
    - `person_profiles: 'identified_only'` reduces costs but means no anonymous user profiles are created
    - When using auto-initialization hooks, config values remain fixed for the session - bootstrapping only works if flags are evaluated on the server before render
    
    </red_flags>
    
    ---
    
    <critical_reminders>
    
    ## CRITICAL REMINDERS
    
    > **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)
    
    **(You MUST initialize posthog-js only in a client/browser context - it requires browser APIs like window and localStorage)**
    
    **(You MUST call `posthog.shutdown()`, `posthog.flush()`, or use `captureImmediate()` after server-side event capture to prevent lost events)**
    
    **(You MUST use `defaults: '2026-01-30'` for automatic SPA page tracking and latest recommended behaviors)**
    
    **Failure to follow these rules will cause lost analytics events, broken tracking, or security vulnerabilities.**
    
    </critical_reminders>
    
    ---
    
    ## Sources
    
    - [PostHog JavaScript SDK](https://posthog.com/docs/libraries/js)
    - [PostHog JavaScript Configuration](https://posthog.com/docs/libraries/js/config)
    - [PostHog Node.js SDK](https://posthog.com/docs/libraries/node)
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related