> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getsabo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Analytics with PostHog

> Integrate PostHog analytics to track user behavior, product usage, and feature adoption in your Sabo application.

<Info>
  Sabo includes pre-configured PostHog analytics with automatic pageview tracking, custom event support, and feature flags. PostHog is optional and only activates when environment variables are set.
</Info>

## Overview

PostHog is an open-source product analytics platform that helps you understand how users interact with your application. Sabo's PostHog integration provides:

* **Automatic pageview tracking** for all routes
* **Custom event tracking** via the `usePostHog()` hook
* **Server-side analytics** for API routes and server components
* **Feature flags** for gradual feature rollouts
* **Session recording** (configurable in PostHog dashboard)
* **Privacy-first** analytics with GDPR compliance options

<Note>
  PostHog is completely optional. If you don't set the environment variables, Sabo will function normally without analytics.
</Note>

<Tip>
  Need a deeper dive into every PostHog feature (insights, dashboards, cohorts)? Keep the official <a href="https://posthog.com/docs" target="_blank" rel="noreferrer">PostHog documentation</a> handy while working through this guide.
</Tip>

***

## Quick Setup

<Steps>
  <Step title="Create PostHog account">
    Sign up for PostHog to get your project API key.

    1. Visit [PostHog Cloud](https://app.posthog.com/signup) or [self-host PostHog](https://posthog.com/docs/self-host)
    2. Create a new project
    3. Copy your **Project API Key** from Project Settings
    4. Note your **PostHog host URL**:
       * Cloud: `https://app.posthog.com` (US) or `https://eu.posthog.com` (EU)
       * Self-hosted: Your custom domain

    <Tip>
      PostHog offers a generous free tier with 1 million events per month. No credit card required for signup.
    </Tip>
  </Step>

  <Step title="Add environment variables">
    Add PostHog credentials to your `.env.local` file:

    ```bash .env.local theme={null}
    # PostHog Analytics (Optional)
    NEXT_PUBLIC_POSTHOG_KEY=phc_your_project_api_key_here
    NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com
    ```

    **Important:** Both variables must be present for PostHog to initialize. If either is missing, PostHog will remain disabled.

    <Check>
      Restart your development server after adding environment variables.
    </Check>
  </Step>

  <Step title="Verify integration">
    Test that PostHog is tracking events:

    1. Start your dev server: `pnpm dev`
    2. Visit `http://localhost:3000`
    3. Open browser console and look for PostHog debug messages (in development mode)
    4. Check PostHog dashboard → Activity to see live pageviews

    <Check>
      In development mode, PostHog automatically enables debug logging. Check your browser console for `[PostHog]` messages.
    </Check>
  </Step>
</Steps>

***

## How It Works

### Architecture

Sabo's PostHog integration consists of three main parts:

1. **Client-side provider** (`src/components/posthog-provider.tsx`)
   * Initializes PostHog in the browser
   * Wraps the entire app to provide PostHog context
   * Enables debug mode in development

2. **Server-side client** (`src/lib/posthog/server.ts`)
   * Tracks events from API routes and server components
   * Uses PostHog Node.js SDK
   * Flushes events immediately for real-time tracking

3. **Exports module** (`src/lib/posthog/index.ts`)
   * Centralizes imports for both client and server utilities
   * Re-exports `usePostHog` hook and `getPostHogClient` function

### Initialization

PostHog initializes automatically when the app loads, but only if environment variables are set.

```tsx src/components/posthog-provider.tsx theme={null}
"use client";

import posthog from "posthog-js";
import { PostHogProvider } from "posthog-js/react";

if (typeof window !== "undefined") {
  const posthogKey = process.env.NEXT_PUBLIC_POSTHOG_KEY;
  const posthogHost = process.env.NEXT_PUBLIC_POSTHOG_HOST;

  if (posthogKey && posthogHost) {
    posthog.init(posthogKey, {
      api_host: posthogHost,
      loaded: (posthog) => {
        if (process.env.NODE_ENV === "development") {
          posthog.debug();  // Enable debug logging in dev
        }
      },
    });
  }
}

export function PHProvider({ children }: { children: React.ReactNode }) {
  return <PostHogProvider client={posthog}>{children}</PostHogProvider>;
}
```

The `PHProvider` wraps your entire app in `src/app/layout.tsx`:

```tsx src/app/layout.tsx theme={null}
import { PHProvider } from "@/components/posthog-provider";

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <PHProvider>
          <ThemeProvider>
            {children}
          </ThemeProvider>
        </PHProvider>
      </body>
    </html>
  );
}
```

<Note>
  `PHProvider` wraps `ThemeProvider` to ensure PostHog context is available throughout the entire application, including all client components.
</Note>

***

## Tracking Events

### Automatic Pageview Tracking

PostHog automatically tracks pageviews for all routes in your app. No additional code required.

**What's tracked:**

* Route changes (including Next.js App Router navigation)
* URL parameters and query strings
* Referrer information
* Browser and device details

**Viewing pageviews:**

1. Go to PostHog Dashboard → Activity
2. Filter by event type: `$pageview`
3. See real-time page visits with full URL paths

### Custom Event Tracking (Client-Side)

Track user interactions, button clicks, form submissions, and other custom events using the `usePostHog()` hook.

<Steps>
  <Step title="Import the hook">
    ```tsx theme={null}
    import { usePostHog } from "@/lib/posthog";
    ```
  </Step>

  <Step title="Call the hook in your component">
    ```tsx theme={null}
    export function MyComponent() {
      const posthog = usePostHog();
      
      // Component logic...
    }
    ```
  </Step>

  <Step title="Track events">
    ```tsx theme={null}
    const handleClick = () => {
      // Track event with properties
      posthog?.capture("button_clicked", {
        button_name: "upgrade_to_pro",
        location: "pricing_page",
        plan: "pro",
      });
    };
    ```
  </Step>
</Steps>

#### Complete Example

```tsx src/components/marketing/pricing.tsx theme={null}
"use client";

import { usePostHog } from "@/lib/posthog";
import { Button } from "@/components/ui/button";

export function PricingCard({ plan }) {
  const posthog = usePostHog();
  
  const handleUpgrade = () => {
    // Track subscription initiation
    posthog?.capture("subscription_started", {
      plan_id: plan.id,
      plan_name: plan.name,
      price: plan.monthlyPrice,
      billing_cycle: "monthly",
    });
    
    // Proceed with checkout...
  };
  
  return (
    <Button onClick={handleUpgrade}>
      Upgrade to {plan.name}
    </Button>
  );
}
```

<Tip>
  Use optional chaining (`posthog?.capture`) to safely handle cases where PostHog is not initialized (e.g., missing environment variables).
</Tip>

#### Common Events to Track

Here are some recommended events to track in a SaaS application:

```tsx theme={null}
// User actions
posthog?.capture("user_signed_up", { method: "email" });
posthog?.capture("user_logged_in", { method: "google_oauth" });
posthog?.capture("user_logged_out");

// Subscription events
posthog?.capture("subscription_started", { plan: "pro", cycle: "yearly" });
posthog?.capture("subscription_cancelled", { reason: "too_expensive" });
posthog?.capture("subscription_upgraded", { from: "free", to: "pro" });

// Feature usage
posthog?.capture("feature_used", { feature_name: "dashboard_export" });
posthog?.capture("search_performed", { query: "analytics", results_count: 12 });
posthog?.capture("file_uploaded", { file_type: "pdf", file_size: 2048 });

// Engagement
posthog?.capture("form_submitted", { form_name: "contact", success: true });
posthog?.capture("video_watched", { video_id: "onboarding", duration: 180 });
posthog?.capture("invite_sent", { invite_count: 3 });
```

### Server-Side Event Tracking

Track events from API routes, server actions, or server components using the server-side PostHog client.

```ts src/app/api/your-route/route.ts theme={null}
import { getPostHogClient } from "@/lib/posthog";

export async function POST(request: Request) {
  const posthog = getPostHogClient();
  
  // Track server-side event
  posthog?.capture({
    distinctId: userId,  // User identifier
    event: "api_call_made",
    properties: {
      endpoint: "/api/data",
      method: "POST",
      response_time: 142,
    },
  });
  
  // Your API logic...
  
  return Response.json({ success: true });
}
```

The server client (`src/lib/posthog/server.ts`) is configured to flush events immediately:

```ts src/lib/posthog/server.ts theme={null}
import { PostHog } from "posthog-node";

let posthogClient: PostHog | null = null;

export function getPostHogClient(): PostHog | null {
  const posthogKey = process.env.NEXT_PUBLIC_POSTHOG_KEY;
  const posthogHost = process.env.NEXT_PUBLIC_POSTHOG_HOST;

  if (!posthogKey || !posthogHost) {
    return null;  // PostHog disabled
  }

  if (!posthogClient) {
    posthogClient = new PostHog(posthogKey, {
      host: posthogHost,
      flushAt: 1,           // Flush after each event
      flushInterval: 0,     // Don't batch events
    });
  }
  
  posthogClient.debug(true);  // Enable debug logging
  return posthogClient;
}
```

<Warning>
  Server-side tracking requires a `distinctId` (usually the user ID) to associate events with specific users. Make sure to provide this when capturing server events.
</Warning>

***

## User Identification

### Identifying Users

Call `posthog.identify()` in whichever component completes your authentication flow (for example, the `/sign-in` page) so future events are tied to the logged-in user:

```tsx theme={null}
"use client";

import { usePostHog } from "@/lib/posthog";

export function SignInForm() {
  const posthog = usePostHog();
  
  const handleSignIn = async (email: string) => {
    const user = await signIn(email); // Your existing auth logic
    
    posthog?.identify(user.id, {
      email: user.email,
      name: user.full_name,
      plan: "free",
      created_at: user.created_at,
    });
    
    posthog?.capture("user_signed_in", { method: "email" });
  };
}
```

### Setting User Properties

Update user properties as they change:

```tsx theme={null}
// Update subscription status
posthog?.people.set({
  plan: "pro",
  mrr: 12,
  billing_cycle: "monthly",
});

// Track trait changes
posthog?.capture("subscription_upgraded", {
  from_plan: "free",
  to_plan: "pro",
});
```

### Resetting User Identity

Clear user identification on logout:

```tsx theme={null}
const handleLogout = async () => {
  await signOut();
  
  // Reset PostHog identity
  posthog?.reset();
};
```

***

## Feature Flags

PostHog feature flags allow you to roll out features gradually, run A/B tests, and toggle features without deploying code.

### Using Feature Flags (Client-Side)

```tsx theme={null}
"use client";

import { usePostHog } from "@/lib/posthog";
import { useEffect, useState } from "react";

export function NewFeature() {
  const posthog = usePostHog();
  const [showNewUI, setShowNewUI] = useState(false);
  
  useEffect(() => {
    // Check feature flag value
    const isEnabled = posthog?.isFeatureEnabled("new-dashboard-ui");
    setShowNewUI(isEnabled ?? false);
  }, [posthog]);
  
  return showNewUI ? <NewDashboard /> : <OldDashboard />;
}
```

### Feature Flag with Variants

```tsx theme={null}
const variant = posthog?.getFeatureFlag("pricing-experiment");

// Show different pricing based on variant
if (variant === "high-price") {
  return <PricingCard price={29} />;
} else if (variant === "low-price") {
  return <PricingCard price={19} />;
} else {
  return <PricingCard price={24} />;  // Default
}
```

### Creating Feature Flags

1. Go to PostHog Dashboard → Feature Flags
2. Click "New feature flag"
3. Configure:
   * **Key**: `new-dashboard-ui` (use in code)
   * **Rollout**: Percentage (e.g., 50% of users)
   * **Filters**: Target specific users, groups, or properties
4. Save and the flag is immediately available

<Tip>
  Feature flags are cached locally, so changes may take a few minutes to propagate to all users. You can force a refresh by calling `posthog?.reloadFeatureFlags()`.
</Tip>

<Note>
  Want to convert those flags into statistically sound experiments? PostHog's <a href="https://posthog.com/docs/experiments" target="_blank" rel="noreferrer">A/B testing guide</a> explains how to define variants, success metrics, and rollouts using the same flag keys shown above.
</Note>

***

## Configuration Options

### PostHog Initialization Options

You can customize PostHog behavior by modifying `src/components/posthog-provider.tsx`:

```tsx src/components/posthog-provider.tsx theme={null}
posthog.init(posthogKey, {
  api_host: posthogHost,
  
  // Session recording (default: disabled)
  disable_session_recording: false,
  
  // Autocapture clicks and form submissions (default: true)
  autocapture: true,
  
  // Capture pageviews automatically (default: true)
  capture_pageview: true,
  
  // Capture pageleave events (default: true)
  capture_pageleave: true,
  
  // Respect Do Not Track browser setting (default: false)
  respect_dnt: true,
  
  // Debug mode (automatically enabled in development)
  loaded: (posthog) => {
    if (process.env.NODE_ENV === "development") {
      posthog.debug();
    }
  },
});
```

### Privacy Options

Configure PostHog to respect user privacy:

```tsx theme={null}
// Disable session recording for sensitive pages
useEffect(() => {
  if (window.location.pathname.includes("/settings/account")) {
    posthog?.stopSessionRecording();
  }
}, [posthog]);

// Opt user out of tracking
posthog?.opt_out_capturing();

// Opt user back in
posthog?.opt_in_capturing();

// Check if user has opted out
const hasOptedOut = posthog?.has_opted_out_capturing();
```

***

## Best Practices

<Tip>
  If you need a formal naming taxonomy for events and properties, refer to PostHog's <a href="https://posthog.com/docs/cdp/transformations/taxonomy-plugin" target="_blank" rel="noreferrer">event taxonomy guide</a>. It includes naming tables, spreadsheet templates, and enforcement strategies that complement the recommendations below.
</Tip>

<AccordionGroup>
  <Accordion title="1. Event Naming Conventions">
    Use consistent, descriptive names for events:

    **Good:**

    * `subscription_started`
    * `user_signed_up`
    * `file_uploaded`

    **Bad:**

    * `click`
    * `event1`
    * `test`

    **Guidelines:**

    * Use `snake_case` for event names
    * Use past tense verbs (`clicked`, not `click`)
    * Be specific (`checkout_completed` vs `purchase`)
    * Group related events with prefixes (`subscription_*`, `user_*`)
  </Accordion>

  <Accordion title="2. Event Properties">
    Include relevant context with every event:

    ```tsx theme={null}
    posthog?.capture("file_uploaded", {
      file_type: "pdf",           // What kind
      file_size: 2048,            // How large
      upload_method: "drag_drop", // How uploaded
      location: "dashboard",      // Where
      timestamp: Date.now(),      // When
    });
    ```

    **Guidelines:**

    * Keep property names consistent across events
    * Include user properties (`plan`, `role`, etc.)
    * Add context (page location, user state)
    * Use appropriate data types (numbers for counts, booleans for flags)
  </Accordion>

  <Accordion title="3. Performance Optimization">
    PostHog batches events by default, but you can optimize further:

    ```tsx theme={null}
    // Debounce high-frequency events
    const debouncedTrack = debounce((searchTerm) => {
      posthog?.capture("search_performed", { query: searchTerm });
    }, 500);

    // Track only on significant actions
    const handleScroll = () => {
      if (scrollPercent > 75 && !tracked) {
        posthog?.capture("article_read_75_percent");
        setTracked(true);
      }
    };
    ```

    **Tips:**

    * Don't track every keystroke or mouse movement
    * Batch similar events (e.g., track scroll milestones: 25%, 50%, 75%, 100%)
    * Use feature flags to disable analytics in staging environments
  </Accordion>

  <Accordion title="4. Testing Analytics">
    Verify events are tracked correctly:

    **In Development:**

    * Check browser console for `[PostHog]` debug messages
    * Visit PostHog Dashboard → Activity (live view)
    * Use PostHog's "Test mode" to see events in real-time

    **Before Production:**

    * Test with different user states (logged in/out, different plans)
    * Verify user identification works after sign-in
    * Check that properties contain expected values
    * Test feature flags with different rollout percentages

    ```tsx theme={null}
    // Add logging in development
    if (process.env.NODE_ENV === "development") {
      console.log("Tracking event:", eventName, properties);
    }
    posthog?.capture(eventName, properties);
    ```
  </Accordion>
</AccordionGroup>

***

## Production Deployment

<Steps>
  <Step title="Update environment variables">
    Set PostHog credentials in your production environment (Vercel, Netlify, etc.):

    ```bash theme={null}
    NEXT_PUBLIC_POSTHOG_KEY=phc_your_production_key
    NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com
    ```

    <Note>
      Use separate PostHog projects for development and production to keep analytics data isolated.
    </Note>
  </Step>

  <Step title="Disable debug mode">
    Ensure debug mode is only enabled in development (already configured in `posthog-provider.tsx`):

    ```tsx theme={null}
    loaded: (posthog) => {
      if (process.env.NODE_ENV === "development") {
        posthog.debug();  // Only in dev
      }
    }
    ```
  </Step>

  <Step title="Configure session recording">
    Decide whether to enable session recording in PostHog Dashboard:

    1. Go to Settings → Project Settings → Recordings
    2. Enable/disable session recording
    3. Configure privacy settings:
       * Mask sensitive inputs (passwords, credit cards)
       * Block specific elements by CSS selector
       * Disable recording on certain pages

    <Warning>
      Session recording captures user interactions. Ensure you comply with GDPR/privacy regulations and disclose this in your privacy policy.
    </Warning>
  </Step>

  <Step title="Set up alerts">
    Configure PostHog to notify you of important events:

    1. Go to Alerts → Create alert
    2. Choose trigger (e.g., "Subscription cancelled" event)
    3. Set threshold and frequency
    4. Connect to Slack, email, or webhooks

    <Check>
      Test alerts in PostHog to ensure notifications are working before going live.
    </Check>
  </Step>
</Steps>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="PostHog not initializing">
    **Symptoms:** No events in PostHog dashboard, no debug messages in console.

    **Causes:**

    * Missing environment variables
    * Incorrect API key or host
    * Variables not prefixed with `NEXT_PUBLIC_`

    **Fix:**

    1. Verify both `NEXT_PUBLIC_POSTHOG_KEY` and `NEXT_PUBLIC_POSTHOG_HOST` are set in `.env.local`
    2. Restart dev server after adding variables
    3. Check browser console for PostHog errors
    4. Confirm API key format: `phc_...`
    5. Use correct host (US: `https://app.posthog.com`, EU: `https://eu.posthog.com`)
  </Accordion>

  <Accordion title="Events not appearing in dashboard">
    **Symptoms:** PostHog initializes but events don't show up.

    **Causes:**

    * Wrong project API key
    * Ad blockers blocking PostHog
    * Network issues
    * PostHog ingestion delay

    **Fix:**

    1. Check you're viewing the correct project in PostHog dashboard
    2. Disable ad blockers (uBlock, Privacy Badger often block analytics)
    3. Check browser network tab for failed requests to PostHog
    4. Wait 1-2 minutes for events to appear (PostHog has slight delay)
    5. Verify API key in PostHog → Project Settings → Project API Key
  </Accordion>

  <Accordion title="'usePostHog is not a function' error">
    **Symptoms:** React error when calling `usePostHog()`.

    **Causes:**

    * Missing `"use client"` directive
    * Component is server component
    * PostHog not imported correctly

    **Fix:**

    1. Add `"use client"` at top of file using `usePostHog()`
    2. Import from `@/lib/posthog`, not `posthog-js` directly
    3. Ensure `PHProvider` wraps your component tree in layout.tsx

    ```tsx theme={null}
    "use client";  // Add this!

    import { usePostHog } from "@/lib/posthog";  // Correct import

    export function MyComponent() {
      const posthog = usePostHog();
      // ...
    }
    ```
  </Accordion>

  <Accordion title="Server-side tracking not working">
    **Symptoms:** `getPostHogClient()` returns `null` on server.

    **Causes:**

    * Environment variables not set in production
    * Variables missing `NEXT_PUBLIC_` prefix

    **Fix:**

    1. Verify environment variables are set in hosting platform (Vercel, Netlify)
    2. Both client and server use `NEXT_PUBLIC_*` variables (this is intentional for consistency)
    3. Redeploy after adding environment variables
    4. Check server logs for PostHog initialization messages
  </Accordion>

  <Accordion title="Feature flags always return false">
    **Symptoms:** `isFeatureEnabled()` always returns false.

    **Causes:**

    * Feature flag not created in PostHog
    * User not identified (`posthog.identify()` not called)
    * Flag filters don't match current user
    * Feature flags not loaded yet

    **Fix:**

    1. Create feature flag in PostHog Dashboard → Feature Flags
    2. Call `posthog.identify(userId)` after authentication
    3. Check flag filters/rollout percentage
    4. Wait for flags to load or call `posthog.reloadFeatureFlags()`

    ```tsx theme={null}
    useEffect(() => {
      // Wait for feature flags to load
      posthog?.onFeatureFlags(() => {
        const isEnabled = posthog.isFeatureEnabled("flag-name");
        setShowFeature(isEnabled);
      });
    }, [posthog]);
    ```
  </Accordion>
</AccordionGroup>

***

## Privacy & GDPR Compliance

PostHog provides tools to help you comply with privacy regulations:

### Cookie Consent

PostHog sets cookies (`ph_*`) to track users. Implement cookie consent before initializing:

```tsx src/components/posthog-provider.tsx theme={null}
if (typeof window !== "undefined") {
  // Check for user consent before initializing
  const hasConsent = getCookieConsent(); // Your consent logic
  
  if (hasConsent && posthogKey && posthogHost) {
    posthog.init(posthogKey, {
      api_host: posthogHost,
      // ...
    });
  }
}
```

### Data Deletion Requests

Handle GDPR deletion requests via PostHog API:

```ts theme={null}
// In your data deletion API route
await fetch(`https://app.posthog.com/api/persons/${userId}`, {
  method: 'DELETE',
  headers: {
    'Authorization': `Bearer ${process.env.POSTHOG_PERSONAL_API_KEY}`
  }
});
```

### Anonymization

Anonymize sensitive data before tracking:

```tsx theme={null}
// Don't track PII directly
posthog?.capture("form_submitted", {
  email_domain: email.split("@")[1],  // Only domain
  user_id_hash: hashUserId(userId),   // Hash instead of raw ID
});
```

<Warning>
  Review your privacy policy to disclose PostHog usage. Sabo includes placeholder legal pages in `src/content/legal/` that mention PostHog—update these with your specific implementation.
</Warning>
