| name | posthog-sdk-patterns |
| description | Production-ready PostHog SDK patterns: singleton client, typed events,
React hooks, Next.js App Router integration, and Python patterns.
Trigger: "posthog SDK patterns", "posthog best practices",
"posthog React hook", "posthog Next.js", "posthog typescript".
|
| allowed-tools | Read, Write, Edit |
| version | 1.12.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
| tags | ["saas","posthog","python","typescript"] |
| compatibility | Designed for Claude Code |
PostHog SDK Patterns
Overview
Production-ready patterns for PostHog integrations: singleton client, type-safe event capture, React hooks for feature flags, Next.js App Router provider, and Python integration patterns.
Prerequisites
posthog-js and/or posthog-node installed
- TypeScript project with strict mode
- React/Next.js (for frontend patterns)
Instructions
Step 1: Singleton Server Client
import { PostHog } from 'posthog-node';
let client: PostHog | null = null;
export function getPostHogServer(): PostHog {
if (!client) {
client = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
host: process.env.POSTHOG_HOST || 'https://us.i.posthog.com',
personalApiKey: process.env.POSTHOG_PERSONAL_API_KEY,
flushAt: 20,
flushInterval: 10000,
});
}
return client;
}
process.on('SIGTERM', async () => {
if (client) await client.shutdown();
process.exit(0);
});
Step 2: Type-Safe Event Capture
type EventMap = {
user_signed_up: { method: 'email' | 'google' | 'github' };
feature_used: { feature_name: string; duration_ms?: number };
subscription_started: { plan: string; interval: 'monthly' | 'annual'; mrr: number };
item_created: { item_type: string; source: 'web' | 'api' };
search_performed: { query: string; results_count: number };
};
export function capture<K extends keyof EventMap>(
distinctId: string,
event: K,
properties: EventMap[K]
) {
const ph = getPostHogServer();
ph.capture({ distinctId, event, properties });
}
capture('user-123', 'subscription_started', {
plan: 'pro',
interval: 'annual',
: ,
});
Step 3: React Feature Flag Hook
'use client';
import { useEffect, useState } from 'react';
import posthog from 'posthog-js';
export function useFeatureFlag(flagKey: string): boolean | undefined {
const [enabled, setEnabled] = useState<boolean | undefined>(undefined);
useEffect(() => {
const current = posthog.isFeatureEnabled(flagKey);
if (current !== undefined) {
setEnabled(current);
}
posthog.onFeatureFlags(() => {
setEnabled(posthog.isFeatureEnabled(flagKey) ?? false);
});
}, [flagKey]);
return enabled;
}
export function useFeatureFlagVariant(flagKey: string): string | undefined {
const [variant, setVariant] = useState<string | undefined>(undefined);
( {
posthog.( {
value = posthog.(flagKey);
( value === ? value : );
});
}, [flagKey]);
variant;
}
() {
newCheckout = ();
(newCheckout === ) ;
newCheckout ? : ;
}
Step 4: Next.js App Router Provider
'use client';
import posthog from 'posthog-js';
import { PostHogProvider } from 'posthog-js/react';
import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect, Suspense } from 'react';
function PostHogPageView() {
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 PHProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
api_host: process.. || ,
: ,
: ,
: {
(process.. === ) ph.();
},
});
}, []);
(
);
}
Step 5: Reverse Proxy for Ad Blocker Bypass
module.exports = {
async rewrites() {
return [
{ source: '/ingest/static/:path*', destination: 'https://us-assets.i.posthog.com/static/:path*' },
{ source: '/ingest/:path*', destination: 'https://us.i.posthog.com/:path*' },
{ source: '/ingest/decide', destination: 'https://us.i.posthog.com/decide' },
];
},
};
Step 6: Python Patterns
import posthog
import os
import atexit
from functools import lru_cache
@lru_cache(maxsize=1)
def get_posthog():
"""Singleton PostHog client."""
posthog.project_api_key = os.environ['POSTHOG_PROJECT_KEY']
posthog.host = os.getenv('POSTHOG_HOST', 'https://us.i.posthog.com')
posthog.personal_api_key = os.getenv('POSTHOG_PERSONAL_API_KEY')
posthog.debug = os.getenv('ENVIRONMENT') == 'development'
return posthog
atexit.register(lambda: get_posthog().shutdown())
def track(distinct_id: str, event: str, properties: dict | None = None):
ph = get_posthog()
ph.capture(distinct_id, event, properties or {})
def is_enabled(flag_key: str, distinct_id: str, default: bool = False) -> bool:
ph = get_posthog()
result = ph.feature_enabled(flag_key, distinct_id)
return result if result is not None else default
Error Handling
| Pattern | Use Case | Benefit |
|---|
| Singleton client | All SDK usage | Prevents multiple initializations |
| Type-safe events | Event capture | Compile-time property validation |
| Feature flag hooks | React components | Auto-updates on flag changes |
| Reverse proxy | Production | Bypasses ad blockers |
| Graceful shutdown | Server processes | No lost events on SIGTERM |
Output
- Singleton PostHog server client with shutdown hook
- Type-safe event capture with TypeScript generics
- React hooks for feature flags (boolean and multivariate)
- Next.js App Router provider with pageview tracking
- Reverse proxy configuration for ad blocker bypass
Resources
Next Steps
Apply patterns in posthog-core-workflow-a for real-world usage.