# PostHog.js Best Practices

Comprehensive best practices for using PostHog.js in production applications, covering initialization, event tracking, feature flags, session recording, privacy, and performance optimization.

## Initialization Best Practices

### Environment-Specific Configuration

```typescript { .api }
// Create environment-aware configuration
const getConfig = (): Partial<PostHogConfig> => {
    const baseConfig: Partial<PostHogConfig> = {
        api_host: process.env.POSTHOG_API_HOST || 'https://us.i.posthog.com',
        autocapture: true,
        capture_pageview: 'history_change',
        person_profiles: 'identified_only'
    };
    
    if (process.env.NODE_ENV === 'production') {
        return {
            ...baseConfig,
            debug: false,
            session_recording: {
                enabled: true,
                maskAllInputs: true,
                consoleLogRecordingEnabled: false
            },
            advanced_disable_toolbar_metrics: true
        };
    }
    
    return {
        ...baseConfig,
        debug: true,
        session_recording: {
            enabled: true,
            consoleLogRecordingEnabled: true
        }
    };
};

posthog.init(process.env.POSTHOG_API_KEY!, getConfig());
```

### Error-Resilient Initialization

```typescript { .api }
// Handle initialization failures gracefully
function initPostHogSafely() {
    try {
        posthog.init(process.env.POSTHOG_API_KEY!, {
            loaded: (posthog) => {
                console.log('PostHog initialized successfully');
            },
            on_request_error: (response) => {
                // Log but don't crash
                console.error('PostHog error:', response.statusCode);
            }
        });
    } catch (error) {
        console.error('PostHog initialization failed:', error);
        // Application continues without analytics
    }
}
```

## Event Tracking Best Practices

### Consistent Event Naming

```typescript { .api }
// Use a consistent naming convention
const EventNames = {
    // User actions (verb_noun)
    BUTTON_CLICKED: 'button_clicked',
    FORM_SUBMITTED: 'form_submitted',
    SEARCH_PERFORMED: 'search_performed',
    
    // Page views
    PAGE_VIEWED: 'page_viewed',
    
    // Business events
    PURCHASE_COMPLETED: 'purchase_completed',
    SUBSCRIPTION_STARTED: 'subscription_started'
} as const;

// Type-safe event tracking
posthog.capture(EventNames.BUTTON_CLICKED, {
    button_id: 'signup',
    button_location: 'header'
});
```

### Rich Event Context

```typescript { .api }
// Include comprehensive context
function trackUserAction(action: string, context: Record<string, any> = {}) {
    posthog.capture(action, {
        ...context,
        // Automatic context
        page_path: window.location.pathname,
        page_url: window.location.href,
        referrer: document.referrer,
        viewport_height: window.innerHeight,
        viewport_width: window.innerWidth,
        timestamp: new Date().toISOString()
    });
}
```

### Avoid Over-Tracking

```typescript { .api }
// ❌ Bad: Track every mouse move
document.addEventListener('mousemove', (e) => {
    posthog.capture('mouse_moved', { x: e.clientX, y: e.clientY });
});

// ✅ Good: Track meaningful interactions
document.querySelectorAll('.cta-button').forEach(button => {
    button.addEventListener('click', () => {
        posthog.capture('cta_clicked', {
            button_text: button.textContent,
            button_location: button.dataset.location
        });
    });
});
```

## Feature Flag Best Practices

### Defensive Flag Checking

```typescript { .api }
// Always provide defaults
function getFeature(flagKey: string, defaultValue: boolean = false): boolean {
    const value = posthog.isFeatureEnabled(flagKey);
    return value !== undefined ? value : defaultValue;
}

// Wait for flags before critical decisions
async function initializeCriticalFeature() {
    // Wait for flags to load
    if (!posthog.featureFlags.hasLoadedFlags) {
        await new Promise(resolve => {
            posthog.onFeatureFlags(() => resolve(undefined))();
        });
    }
    
    if (getFeature('critical-feature', false)) {
        await loadCriticalFeature();
    }
}
```

### Flag-Driven Configuration

```typescript { .api }
// Use flags for dynamic configuration
interface AppConfig {
    maxRetries: number;
    timeout: number;
    endpoint: string;
}

function getAppConfig(): AppConfig {
    const flagPayload = posthog.getFeatureFlagPayload('app-config') as Partial<AppConfig>;
    
    const defaults: AppConfig = {
        maxRetries: 3,
        timeout: 5000,
        endpoint: '/api/v1'
    };
    
    return {
        ...defaults,
        ...flagPayload
    };
}
```

### Experiment Tracking

```typescript { .api }
// Track experiment exposure
function trackExperimentExposure(experimentKey: string) {
    const variant = posthog.getFeatureFlag(experimentKey);
    
    if (variant) {
        posthog.capture('experiment_exposure', {
            experiment_key: experimentKey,
            variant: variant,
            session_id: posthog.get_session_id(),
            exposure_time: Date.now()
        });
    }
}
```

## Session Recording Best Practices

### Privacy-First Recording

```typescript { .api }
// Configure with privacy in mind
posthog.init('token', {
    session_recording: {
        enabled: true,
        // Mask sensitive inputs
        maskAllInputs: true,
        // Block sensitive sections
        blockSelector: '.sensitive-data, .payment-info, .ssn',
        // Mask specific text
        maskTextSelector: '.email, .phone, .address',
        // Control network capture
        networkPayloadCapture: {
            recordHeaders: false,
            recordBody: ['application/json']  // Only JSON
        }
    }
});
```

### Conditional Recording

```typescript { .api }
// Only record important user flows
const recordedPaths = ['/checkout', '/onboarding', '/dashboard'];

router.on('route', (route) => {
    const shouldRecord = recordedPaths.some(path => 
        route.path.startsWith(path)
    );
    
    if (shouldRecord && !posthog.sessionRecordingStarted()) {
        posthog.startSessionRecording();
    } else if (!shouldRecord && posthog.sessionRecordingStarted()) {
        posthog.stopSessionRecording();
    }
});
```

### Link Recordings to Errors

```typescript { .api }
// Always link session replays to errors
window.addEventListener('error', (event) => {
    posthog.captureException(event.error, {
        session_replay_url: posthog.get_session_replay_url({
            withTimestamp: true,
            timestampLookBack: 30
        }),
        session_id: posthog.get_session_id(),
        page_url: window.location.href,
        user_agent: navigator.userAgent
    });
});
```

## User Identification Best Practices

### Progressive Identification

```typescript { .api }
// Identify progressively as you learn more
class UserIdentification {
    identifyAnonymous() {
        // Let PostHog create anonymous ID
        posthog.capture('app_opened');
    }
    
    identifyBasic(userId: string, email: string) {
        // Basic identification after signup
        posthog.alias(userId);
        posthog.identify(userId, {
            email: email
        }, {
            signup_date: new Date().toISOString()
        });
    }
    
    enrichProfile(properties: Record<string, any>) {
        // Add more properties as they become available
        posthog.setPersonProperties(properties);
    }
    
    resetOnLogout() {
        // Always reset on logout
        posthog.reset();
    }
}
```

### Handle Multiple Identity Sources

```typescript { .api }
// Handle OAuth and multiple login methods
async function handleLogin(provider: 'google' | 'github' | 'email', credentials: any) {
    const user = await authenticate(provider, credentials);
    
    // Create alias from anonymous to identified
    posthog.alias(user.id);
    
    // Identify with provider info
    posthog.identify(user.id, {
        email: user.email,
        name: user.name,
        auth_provider: provider,
        verified: user.verified
    }, {
        first_login_provider: provider,
        first_login_date: new Date().toISOString()
    });
    
    // Reload flags for personalized experience
    posthog.reloadFeatureFlags();
}
```

## Privacy & Compliance Best Practices

### GDPR-Compliant Implementation

```typescript { .api }
// Implement opt-in by default for GDPR
posthog.init('token', {
    opt_out_capturing_by_default: true,
    opt_out_persistence_by_default: true,
    respect_dnt: true
});

// Show consent banner
function showConsentBanner() {
    // Display UI
    const banner = createConsentBanner({
        onAccept: () => {
            posthog.opt_in_capturing({
                captureEventName: 'gdpr_consent_granted',
                captureProperties: {
                    consent_version: '2.0',
                    consent_date: new Date().toISOString()
                }
            });
            
            // Enable features after consent
            posthog.startSessionRecording();
        },
        onReject: () => {
            posthog.opt_out_capturing();
        }
    });
}

// Check consent on load
if (posthog.get_explicit_consent_status() === 'pending') {
    showConsentBanner();
}
```

### Data Minimization

```typescript { .api }
// Only capture necessary data
posthog.init('token', {
    // Deny list for sensitive properties
    property_denylist: [
        'password',
        'credit_card',
        'ssn',
        'api_key',
        'access_token',
        'secret'
    ],
    // Mask personal data
    mask_personal_data_properties: true,
    custom_personal_data_properties: [
        'email',
        'phone',
        'address',
        'ip_address'
    ]
});
```

## Performance Best Practices

### Optimize Bundle Size

```typescript { .api }
// Lazy load PostHog for better initial page load
async function loadPostHogAsync() {
    const { default: posthog } = await import('posthog-js');
    
    posthog.init('token', {
        loaded: (ph) => {
            console.log('PostHog loaded asynchronously');
        }
    });
    
    return posthog;
}

// Load after critical content
window.addEventListener('load', () => {
    loadPostHogAsync();
});
```

### Batch Configuration for High Traffic

```typescript { .api }
// Optimize for high-traffic pages
posthog.init('token', {
    request_batching: true,
    batch_size: 100,          // Larger batches
    batch_max_wait_ms: 10000, // Less frequent sends
    disable_compression: false // Keep compression on
});
```

### Selective Feature Loading

```typescript { .api }
// Only load features you need
posthog.init('token', {
    // Disable unused features
    disable_surveys: true,
    disable_product_tours: true,
    disable_conversations: true,
    
    // Disable expensive features on low-end devices
    session_recording: {
        enabled: !isLowEndDevice(),
        recordCanvas: false  // Canvas recording is expensive
    }
});

function isLowEndDevice(): boolean {
    return (
        navigator.deviceMemory && navigator.deviceMemory < 4 ||
        navigator.hardwareConcurrency && navigator.hardwareConcurrency < 4
    );
}
```

## Error Handling Best Practices

### Comprehensive Error Tracking

```typescript { .api }
// Set up global error handlers
class ErrorTracker {
    initialize() {
        // Uncaught errors
        window.addEventListener('error', (event) => {
            this.trackError(event.error, {
                type: 'uncaught_error',
                filename: event.filename,
                lineno: event.lineno,
                colno: event.colno
            });
        });
        
        // Unhandled promise rejections
        window.addEventListener('unhandledrejection', (event) => {
            this.trackError(event.reason, {
                type: 'unhandled_rejection',
                promise: String(event.promise)
            });
        });
        
        // Enable PostHog exception autocapture
        posthog.startExceptionAutocapture({
            capture_unhandled_errors: true,
            capture_unhandled_rejections: true,
            capture_console_errors: false  // We handle console separately
        });
    }
    
    trackError(error: Error, context: Record<string, any>) {
        // Deduplicate errors
        const fingerprint = this.getErrorFingerprint(error);
        if (this.recentErrors.has(fingerprint)) {
            return;
        }
        this.recentErrors.add(fingerprint);
        
        posthog.captureException(error, {
            ...context,
            session_replay_url: posthog.get_session_replay_url({
                withTimestamp: true,
                timestampLookBack: 30
            }),
            user_id: posthog.get_distinct_id(),
            session_id: posthog.get_session_id()
        });
    }
    
    private recentErrors = new Set<string>();
    
    private getErrorFingerprint(error: Error): string {
        return `${error.name}:${error.message}:${error.stack?.split('\n')[1]}`;
    }
}
```

## Testing Best Practices

### Mock PostHog in Tests

```typescript { .api }
// Create PostHog mock for testing
const mockPostHog = {
    init: jest.fn(),
    capture: jest.fn(),
    identify: jest.fn(),
    isFeatureEnabled: jest.fn(() => false),
    getFeatureFlag: jest.fn(() => undefined),
    onFeatureFlags: jest.fn((cb) => {
        cb({}, {});
        return () => {};
    })
};

// Use in tests
beforeEach(() => {
    (window as any).posthog = mockPostHog;
});

afterEach(() => {
    jest.clearAllMocks();
});

// Test with feature flags
it('shows new feature when flag is enabled', () => {
    mockPostHog.isFeatureEnabled.mockReturnValue(true);
    
    render(<Component />);
    
    expect(screen.getByText('New Feature')).toBeInTheDocument();
});
```

### E2E Testing with PostHog

```typescript { .api }
// Disable PostHog in E2E tests or use test environment
if (process.env.E2E_TEST) {
    posthog.init('token', {
        api_host: 'https://test.posthog.com',
        // Or disable completely
        opt_out_capturing_by_default: true
    });
}

// Override flags for specific test scenarios
if (process.env.TEST_VARIANT === 'control') {
    posthog.featureFlags.overrideFeatureFlags({
        'experiment': 'control'
    });
}
```

## Monitoring Best Practices

### Health Checks

```typescript { .api }
// Monitor PostHog health
class PostHogMonitor {
    checkHealth() {
        return {
            initialized: !!posthog,
            capturing: posthog.is_capturing(),
            flagsLoaded: posthog.featureFlags.hasLoadedFlags,
            sessionRecording: posthog.sessionRecordingStarted(),
            config: {
                apiHost: posthog.config.api_host,
                persistence: posthog.config.persistence
            }
        };
    }
    
    logHealthStatus() {
        const health = this.checkHealth();
        console.log('PostHog Health:', health);
        
        if (!health.capturing) {
            console.warn('PostHog not capturing events');
        }
        
        if (!health.flagsLoaded) {
            console.warn('Feature flags not loaded');
        }
    }
}

// Check health on critical paths
async function initializeApp() {
    const monitor = new PostHogMonitor();
    
    // Wait for PostHog to be ready
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    monitor.logHealthStatus();
    
    // Continue with app initialization
}
```

## TypeScript Best Practices

### Type-Safe Events and Properties

```typescript { .api }
// Define event types
interface EventMap {
    'button_clicked': {
        button_id: string;
        button_text: string;
        location: string;
    };
    'page_viewed': {
        page_path: string;
        page_title: string;
    };
    'purchase_completed': {
        order_id: string;
        total: number;
        currency: string;
        items: Array<{
            id: string;
            quantity: number;
        }>;
    };
}

// Type-safe capture function
function captureEvent<K extends keyof EventMap>(
    event: K,
    properties: EventMap[K]
): void {
    posthog.capture(event, properties);
}

// Usage with type checking
captureEvent('button_clicked', {
    button_id: 'signup',
    button_text: 'Sign Up',
    location: 'header'
});

// ❌ Type error: missing required properties
// captureEvent('button_clicked', { button_id: 'signup' });
```

### Type-Safe Feature Flags

```typescript { .api }
// Define flag types
interface FeatureFlags {
    'new-dashboard': boolean;
    'button-color': 'blue' | 'green' | 'red';
    'experiment-variant': 'control' | 'variant-a' | 'variant-b';
}

// Type-safe flag getter
function getFeatureFlag<K extends keyof FeatureFlags>(
    key: K
): FeatureFlags[K] | undefined {
    return posthog.getFeatureFlag(key) as FeatureFlags[K] | undefined;
}

// Usage with type inference
const buttonColor = getFeatureFlag('button-color');  // 'blue' | 'green' | 'red' | undefined
const isDashboardNew = getFeatureFlag('new-dashboard');  // boolean | undefined
```

## Production Deployment Checklist

- [ ] Environment-specific configuration for dev/staging/prod
- [ ] GDPR-compliant consent mechanism implemented
- [ ] Session recording privacy settings configured (mask inputs, block selectors)
- [ ] Error tracking with session replay links
- [ ] Feature flags loaded before critical decisions
- [ ] TypeScript types defined for events and flags
- [ ] Performance optimizations applied (batching, selective features)
- [ ] Monitoring and health checks in place
- [ ] Testing strategy for analytics (mocks, E2E)
- [ ] Privacy policy updated to reflect PostHog usage
- [ ] Data retention policies configured in PostHog dashboard
- [ ] Debug mode disabled in production
- [ ] CSP headers allow PostHog domains
- [ ] CORS configured for api_host
- [ ] Ad blocker detection and fallbacks implemented

## Common Anti-Patterns to Avoid

### Don't Block Initialization

```typescript { .api }
// ❌ Bad: Wait for PostHog before rendering
async function initApp() {
    await initializePostHog();  // Don't block on this
    render(<App />);
}

// ✅ Good: Initialize in parallel
function initApp() {
    initializePostHog();  // Fire and forget
    render(<App />);
}
```

### Don't Check Flags Synchronously on Load

```typescript { .api }
// ❌ Bad: Check flag immediately
function Component() {
    const showNew = posthog.isFeatureEnabled('new-ui');  // May be undefined!
    return showNew ? <NewUI /> : <OldUI />;
}

// ✅ Good: Handle loading state
function Component() {
    const [showNew, setShowNew] = useState<boolean | null>(null);
    
    useEffect(() => {
        posthog.onFeatureFlags((flags) => {
            setShowNew(flags['new-ui'] || false);
        });
    }, []);
    
    if (showNew === null) return <Loading />;
    return showNew ? <NewUI /> : <OldUI />;
}
```

### Don't Identify Without Aliasing

```typescript { .api }
// ❌ Bad: Identify without alias (loses anonymous events)
function onSignup(userId: string) {
    posthog.identify(userId);
}

// ✅ Good: Alias first to link anonymous events
function onSignup(userId: string) {
    posthog.alias(userId);  // Link anonymous ID to user ID
    posthog.identify(userId, {
        signup_date: new Date().toISOString()
    });
}
```

### Don't Ignore Errors

```typescript { .api }
// ❌ Bad: Silently fail
try {
    posthog.capture('event');
} catch (error) {
    // Ignore
}

// ✅ Good: Log and monitor
try {
    posthog.capture('event');
} catch (error) {
    console.error('Analytics error:', error);
    // Track to error monitoring service
    errorMonitor.captureException(error);
}
```

## Additional Resources

- [Initialization](../reference/initialization.md) - Complete configuration reference
- [Events](../reference/events.md) - Event tracking guide
- [Feature Flags](../reference/feature-flags.md) - Feature flag implementation
- [Session Recording](../reference/session-recording.md) - Recording setup and privacy
- [Privacy & Consent](../reference/privacy.md) - GDPR and privacy compliance
- [Error Tracking](../reference/error-tracking.md) - Exception handling
- [Advanced Features](../reference/advanced.md) - LLM analytics, groups, and more
