CentercodePorygon
DittoPorygonAPI

Analytics

Product analytics and feature flags with PostHog

Safe

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 { Suspense } from 'react';
import { PostHogProvider, PostHogPageView } from '@/lib/analytics';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <PostHogProvider>
          <Suspense fallback={null}>
            <PostHogPageView />
          </Suspense>
          {children}
        </PostHogProvider>
      </body>
    </html>
  );
}

// Environment variables (optional - gracefully disabled if not set)
// NEXT_PUBLIC_POSTHOG_KEY=phc_...
// NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com

// Or use the pre-configured provider from ui/providers:
// import { PostHogProvider } from '@/ui/providers/posthog-provider';
// This wrapper already includes Suspense and PostHogPageView.

Optional: 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 (using posthog-js directly)
// Note: Our typed wrapper exposes core tracking methods.
// For feature flags, access the full PostHog client via the CDN script.

// In a component:
declare global {
  interface Window {
    posthog?: {
      isFeatureEnabled: (key: string) => boolean | undefined;
      getFeatureFlag: (key: string) => string | boolean | undefined;
    };
  }
}

// Check if feature is enabled
if (typeof window !== 'undefined' && window.posthog?.isFeatureEnabled?.('new_dashboard')) {
  // Show new dashboard
}

// Get feature flag variant
const variant = typeof window !== 'undefined'
  ? window.posthog?.getFeatureFlag?.('pricing_experiment')
  : undefined;
if (variant === 'premium_first') {
  // Show premium plan first
}

// Feature flag with fallback
const showBetaFeature =
  (typeof window !== 'undefined' && window.posthog?.isFeatureEnabled?.('beta_feature')) ?? false;

Server-Side Tracking

Tracking events from API routes.

// Server-side tracking (API routes)
// NOTE: posthog-node is NOT currently installed in this project.
// Client-side tracking via posthog-js is our primary approach.
//
// If you need server-side tracking:
// 1. Install: pnpm add posthog-node
// 2. Add env vars: POSTHOG_API_KEY and POSTHOG_HOST
// 3. Use the pattern below:

/*
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 });
}
*/

// Current approach: Track meaningful events client-side after
// API responses, or use audit logging for server-side tracking.

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

Related

Logging

Server-side observability

Error Handling

Error tracking patterns

External Documentation

PostHog Documentation|Feature Flags Guide