Overview
Use PostHog for product analytics, user tracking, and feature flags. The integration is optional and gracefully disabled if not configured.
What it is
PostHog client integration with React components for page views and utilities for event tracking.
Why we use it
Privacy-focused analytics, powerful feature flags, session recording, and self-hostable option.
When to use
Track user behavior, measure feature adoption, run A/B tests, or gradually roll out new features.
Key Features
- trackEvent() for custom event tracking
- identifyUser() to link sessions to users
- Feature flags for gradual rollouts
- Gracefully disabled if not configured
Quick Start
Tracking Events
Basic event tracking and user identification.
// Track a custom event
import { trackEvent } from '@/lib/analytics';
// Track user action
trackEvent('project_created', {
projectId: project.id,
programId: project.programId,
hasDescription: !!project.description,
});
// Identify a user after login
import { identifyUser } from '@/lib/analytics';
identifyUser(user.id, {
email: user.email,
name: user.name,
role: user.role,
});
// Reset on logout
import { resetUser } from '@/lib/analytics';
resetUser();Patterns
Provider Setup
Adding PostHog to your app layout.
// src/app/[locale]/layout.tsx
import { PostHogProvider, PostHogPageView } from '@/lib/analytics';
export default function RootLayout({ children }) {
return (
<html>
<body>
<PostHogProvider>
<PostHogPageView />
{children}
</PostHogProvider>
</body>
</html>
);
}
// Environment variables (optional - gracefully disabled if not set)
// NEXT_PUBLIC_POSTHOG_KEY=phc_...
// NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.comOptional: If NEXT_PUBLIC_POSTHOG_KEY is not set, analytics is gracefully disabled. No errors, no tracking.
Event Tracking
Tracking meaningful user actions.
// Track meaningful user actions
import { trackEvent } from '@/lib/analytics';
// Feature usage
trackEvent('feedback_submitted', {
projectId,
category: feedback.category,
wordCount: feedback.text.split(' ').length,
});
// Conversion events
trackEvent('tester_invited', {
programId,
inviteCount: emails.length,
source: 'bulk_invite',
});
// Error tracking
trackEvent('validation_error', {
field: 'email',
errorType: 'invalid_format',
page: '/signup',
});Feature Flags
Checking feature flags in components.
// Feature flags with PostHog
import { getPostHog } from '@/lib/analytics';
// Check if feature is enabled
const posthog = getPostHog();
if (posthog?.isFeatureEnabled('new_dashboard')) {
// Show new dashboard
}
// Get feature flag payload
const variant = posthog?.getFeatureFlag('pricing_experiment');
if (variant === 'premium_first') {
// Show premium plan first
}
// Feature flag with fallback
const showBetaFeature = posthog?.isFeatureEnabled('beta_feature') ?? false;Server-Side Tracking
Tracking events from API routes.
// Server-side tracking (API routes)
import { PostHog } from 'posthog-node';
const posthog = new PostHog(
process.env.POSTHOG_API_KEY!,
{ host: process.env.POSTHOG_HOST }
);
export async function POST(request: Request) {
// ... handle request
// Track server-side event
posthog.capture({
distinctId: userId,
event: 'api_request',
properties: {
endpoint: '/api/v1/projects',
method: 'POST',
statusCode: 201,
},
});
// Flush before response
await posthog.shutdown();
return NextResponse.json({ ok: true });
}Watch Out
Tracking personal information (emails, names)
Don't
// Tracking personal data
trackEvent('user_signed_up', {
email: user.email, // PII!
password: password, // NEVER!
creditCard: cardNumber, // NEVER!
});Do
// Track anonymized data only
trackEvent('user_signed_up', {
userId: user.id, // ID only
role: user.role,
source: signupSource,
hasOrganization: !!user.orgId,
});- Tracking too many events (noise)
- Not respecting user consent preferences
- Tracking events without clear purpose