or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/posthog-js@1.335.x

docs

examples

edge-cases.mdreal-world-scenarios.md
index.md
tile.json

tessl/npm-posthog-js

tessl install tessl/npm-posthog-js@1.335.0

PostHog Browser JS Library is a comprehensive browser analytics and feature management SDK that enables developers to capture user events, track product analytics, manage feature flags, record session replays, and implement feedback mechanisms like surveys and conversations in web applications.

edge-cases.mddocs/examples/

PostHog.js Edge Cases and Advanced Scenarios

Handling edge cases, race conditions, and advanced scenarios.

Race Conditions

Feature Flags Not Loaded Yet

// ❌ Bad: Check flag before it's loaded
function Component() {
    const enabled = posthog.isFeatureEnabled('new-ui');  // May be undefined
    return enabled ? <NewUI /> : <OldUI />;
}

// ✅ Good: Wait for flags to load
function Component() {
    const [flagsLoaded, setFlagsLoaded] = useState(false);
    const [enabled, setEnabled] = useState(false);
    
    useEffect(() => {
        const unsubscribe = posthog.onFeatureFlags((flags) => {
            setEnabled(flags['new-ui'] || false);
            setFlagsLoaded(true);
        });
        return unsubscribe;
    }, []);
    
    if (!flagsLoaded) return <Loading />;
    return enabled ? <NewUI /> : <OldUI />;
}

Async Initialization

// Ensure PostHog is ready before use
async function initializeApp() {
    return new Promise<void>((resolve) => {
        posthog.init('token', {
            loaded: () => {
                console.log('PostHog ready');
                resolve();
            }
        });
    });
}

// Wait for initialization
await initializeApp();
posthog.capture('app_initialized');

Network Failures

Offline Handling

// Handle offline scenarios gracefully
window.addEventListener('online', () => {
    console.log('Back online - PostHog will resume');
    posthog.capture('connection_restored');
});

window.addEventListener('offline', () => {
    console.log('Offline - PostHog will queue events');
    // Events are automatically queued and sent when connection returns
});

// Check connectivity before critical operations
if (navigator.onLine) {
    posthog.reloadFeatureFlags();
}

API Host Unreachable

// Fallback when API is unreachable
posthog.init('token', {
    api_host: 'https://us.i.posthog.com',
    on_request_error: (response) => {
        console.error('PostHog request failed:', response.statusCode);
        
        // Log to alternative service
        logToBackupService({
            error: 'PostHog unreachable',
            status: response.statusCode
        });
    }
});

Browser Storage Issues

LocalStorage Blocked

// Handle blocked storage
try {
    posthog.init('token', {
        persistence: 'localStorage+cookie'
    });
} catch (error) {
    console.warn('Storage blocked, using memory persistence');
    
    // Fallback to memory-only
    posthog.init('token', {
        persistence: 'memory'
    });
}

Cookie Restrictions

// Handle third-party cookie restrictions
posthog.init('token', {
    persistence: 'localStorage',  // Avoid cookies
    cross_subdomain_cookie: false,
    secure_cookie: true
});

Multiple Initialization

Prevent Double Initialization

// Singleton pattern for PostHog
let posthogInitialized = false;

export function initPostHog() {
    if (posthogInitialized) {
        console.warn('PostHog already initialized');
        return;
    }
    
    posthog.init('token', {
        loaded: () => {
            posthogInitialized = true;
        }
    });
}

// Safe to call multiple times
initPostHog();
initPostHog();  // Will warn and skip

Session Transitions

Handle Session ID Changes

// Track session transitions
let currentSessionId = posthog.get_session_id();

posthog.onSessionId((newSessionId, windowId) => {
    console.log('Session changed:', {
        old: currentSessionId,
        new: newSessionId
    });
    
    // Track transition
    posthog.capture('session_transitioned', {
        previous_session: currentSessionId,
        new_session: newSessionId,
        window_id: windowId
    });
    
    currentSessionId = newSessionId;
});

User Identity Edge Cases

Anonymous to Identified Transition

// Properly link anonymous and identified users
async function handleSignup(userData: SignupData) {
    const anonymousId = posthog.get_distinct_id();
    
    // Create user account
    const user = await createAccount(userData);
    
    // Link anonymous activity to new user
    posthog.alias(user.id);
    
    // Identify with properties
    posthog.identify(user.id, {
        email: user.email,
        name: user.name
    }, {
        signup_date: new Date().toISOString(),
        initial_referrer: document.referrer
    });
    
    // Track the conversion
    posthog.capture('user_signed_up', {
        previous_id: anonymousId,
        new_id: user.id
    });
}

Multiple Device Tracking

// Handle same user across devices
function identifyAcrossDevices(userId: string, deviceId: string) {
    posthog.identify(userId, {
        email: user.email
    });
    
    // Track device
    posthog.capture('device_identified', {
        device_id: deviceId,
        device_type: getDeviceType(),
        first_seen: isNewDevice(deviceId)
    });
    
    // Set device as super property
    posthog.register({
        device_id: deviceId
    });
}

Configuration Conflicts

Resolving Conflicting Settings

// ❌ Bad: Conflicting configuration
posthog.init('token', {
    disable_persistence: true,
    persistence: 'localStorage'  // Ignored!
});

// ✅ Good: Consistent configuration
posthog.init('token', {
    disable_persistence: false,
    persistence: 'localStorage'
});

// ✅ Good: Validate configuration
function validateAndInit(config: Partial<PostHogConfig>) {
    if (config.disable_persistence && config.persistence !== 'memory') {
        console.warn('Conflict: disable_persistence overrides persistence setting');
        config.persistence = 'memory';
    }
    
    posthog.init('token', config);
}

High-Traffic Scenarios

Event Flooding Prevention

// Prevent event flooding
class EventThrottler {
    private lastCapture = new Map<string, number>();
    private throttleMs = 1000;
    
    capture(event: string, properties: Properties = {}) {
        const now = Date.now();
        const last = this.lastCapture.get(event);
        
        if (last && (now - last) < this.throttleMs) {
            console.log('Throttling event:', event);
            return;
        }
        
        this.lastCapture.set(event, now);
        posthog.capture(event, properties);
    }
}

const throttler = new EventThrottler();

// Use for high-frequency events
window.addEventListener('scroll', () => {
    throttler.capture('scrolled', {
        position: window.scrollY
    });
});

Memory Management

Long-Running Sessions

// Manage memory in long-running sessions
class SessionManager {
    private sessionStartTime = Date.now();
    private readonly MAX_SESSION_TIME = 4 * 60 * 60 * 1000;  // 4 hours
    
    initialize() {
        setInterval(() => {
            this.checkSessionAge();
        }, 60000);  // Check every minute
    }
    
    checkSessionAge() {
        const age = Date.now() - this.sessionStartTime;
        
        if (age > this.MAX_SESSION_TIME) {
            console.log('Session too old, resetting');
            
            // Capture session end
            posthog.capture('long_session_reset', {
                session_duration_ms: age
            });
            
            // Reset PostHog state
            posthog.reset();
            
            // Restart
            this.sessionStartTime = Date.now();
        }
    }
}

Browser Compatibility

Polyfill Requirements

// Check for required features
function checkBrowserSupport(): boolean {
    const required = {
        localStorage: typeof window.localStorage !== 'undefined',
        fetch: typeof window.fetch !== 'undefined',
        Promise: typeof Promise !== 'undefined',
        JSON: typeof JSON !== 'undefined'
    };
    
    const unsupported = Object.entries(required)
        .filter(([key, supported]) => !supported)
        .map(([key]) => key);
    
    if (unsupported.length > 0) {
        console.error('Unsupported features:', unsupported);
        return false;
    }
    
    return true;
}

// Initialize only if supported
if (checkBrowserSupport()) {
    posthog.init('token');
} else {
    console.warn('Browser not supported, PostHog disabled');
}

Performance Edge Cases

Low-End Device Detection

// Adapt to device capabilities
function getOptimalConfig(): Partial<PostHogConfig> {
    const isLowEnd = (
        navigator.deviceMemory && navigator.deviceMemory < 4 ||
        navigator.hardwareConcurrency && navigator.hardwareConcurrency < 4
    );
    
    if (isLowEnd) {
        return {
            session_recording: {
                enabled: false  // Disable expensive features
            },
            capture_heatmaps: false,
            autocapture: {
                capture_clicks: true,
                rageclicks: false,
                dead_clicks: false
            }
        };
    }
    
    return {
        session_recording: {
            enabled: true,
            recordCanvas: true
        },
        capture_heatmaps: true
    };
}

posthog.init('token', getOptimalConfig());

Testing Edge Cases

Mock PostHog in Tests

// Complete PostHog mock
const createPostHogMock = () => ({
    init: jest.fn(),
    capture: jest.fn(),
    identify: jest.fn(),
    alias: jest.fn(),
    reset: jest.fn(),
    isFeatureEnabled: jest.fn(() => false),
    getFeatureFlag: jest.fn(() => undefined),
    onFeatureFlags: jest.fn((cb) => {
        cb({}, {});
        return () => {};
    }),
    startSessionRecording: jest.fn(),
    stopSessionRecording: jest.fn(),
    get_distinct_id: jest.fn(() => 'test-user'),
    get_session_id: jest.fn(() => 'test-session'),
    featureFlags: {
        hasLoadedFlags: true,
        getFlags: jest.fn(() => [])
    }
});

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

Security Edge Cases

XSS Prevention

// Sanitize user input before tracking
function sanitizeProperties(properties: Properties): Properties {
    const sanitized: Properties = {};
    
    for (const [key, value] of Object.entries(properties)) {
        if (typeof value === 'string') {
            // Remove potential XSS
            sanitized[key] = value
                .replace(/<script[^>]*>.*?<\/script>/gi, '')
                .replace(/<[^>]+>/g, '')
                .substring(0, 1000);  // Limit length
        } else {
            sanitized[key] = value;
        }
    }
    
    return sanitized;
}

// Use when tracking user-generated content
posthog.capture('comment_posted', sanitizeProperties({
    comment_text: userInput,
    user_id: userId
}));

Migration Edge Cases

Gradual Migration

// Run PostHog alongside existing analytics
function dualTrack(event: string, properties: Properties) {
    // Track to both systems during migration
    posthog.capture(event, properties);
    legacyAnalytics.track(event, properties);
}

// Gradually phase out old system
const migrationPercent = 50;  // 50% on PostHog

function smartTrack(event: string, properties: Properties) {
    if (Math.random() * 100 < migrationPercent) {
        posthog.capture(event, properties);
    } else {
        legacyAnalytics.track(event, properties);
    }
}

See Also

  • Quick Start - Basic setup
  • Best Practices - Production patterns
  • Real-World Scenarios - Common use cases
  • API Reference - Detailed documentation