Analytics
PostHog analytics for web and mobile — events, feature flags, session replay
Coldstart generates PostHog analytics when your project includes a web or mobile platform. Each platform gets its own SDK integration, auto-initialized at app startup.
What's generated
posthog-js with a React provider:
src/lib/posthog.ts — PostHog client initialization:
import posthog from "posthog-js";
export const initPostHog = () => {
if (typeof window !== "undefined" && process.env.NEXT_PUBLIC_POSTHOG_KEY) {
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://eu.i.posthog.com",
});
}
};
export { posthog };src/components/posthog-provider.tsx — wraps the app in the root layout:
"use client";
import { useEffect } from "react";
import { initPostHog } from "@/lib/posthog";
export const PostHogProvider = ({ children }: { children: React.ReactNode }) => {
useEffect(() => {
initPostHog();
}, []);
return <>{children}</>;
};The provider is automatically added to the root layout. PostHog only initializes when NEXT_PUBLIC_POSTHOG_KEY is set, so it's safe in development without credentials.
posthog-react-native with helper functions:
lib/posthog.ts — async initialization, disabled in dev:
import PostHog from "posthog-react-native";
let posthogClient: PostHog | null = null;
export const initPostHog = async () => {
const apiKey = process.env.EXPO_PUBLIC_POSTHOG_KEY;
if (!apiKey || __DEV__) return;
posthogClient = await PostHog.initAsync(apiKey, {
host: process.env.EXPO_PUBLIC_POSTHOG_HOST || "https://eu.i.posthog.com",
});
};
export const getPostHog = () => posthogClient;lib/analytics.ts — typed helper functions:
import { getPostHog } from "./posthog";
export const trackEvent = (event: string, properties?: Record<string, unknown>) => {
getPostHog()?.capture(event, properties);
};
export const identifyUser = (userId: string, traits?: Record<string, unknown>) => {
getPostHog()?.identify(userId, traits);
};
export const resetUser = () => {
getPostHog()?.reset();
};
export const trackScreen = (screenName: string, properties?: Record<string, unknown>) => {
getPostHog()?.screen(screenName, properties);
};initPostHog() is called in the root _layout.tsx via useEffect. It's a no-op in development (__DEV__).
Environment variables
| Variable | Required | Description |
|---|---|---|
NEXT_PUBLIC_POSTHOG_KEY | Yes | PostHog project API key |
NEXT_PUBLIC_POSTHOG_HOST | No | PostHog instance URL (defaults to https://eu.i.posthog.com) |
Add to apps/web/.env:
NEXT_PUBLIC_POSTHOG_KEY=phc_your_project_key
NEXT_PUBLIC_POSTHOG_HOST=https://eu.i.posthog.com| Variable | Required | Description |
|---|---|---|
EXPO_PUBLIC_POSTHOG_KEY | Yes | PostHog project API key |
EXPO_PUBLIC_POSTHOG_HOST | No | PostHog instance URL (defaults to https://eu.i.posthog.com) |
Add to apps/mobile/.env:
EXPO_PUBLIC_POSTHOG_KEY=phc_your_project_key
EXPO_PUBLIC_POSTHOG_HOST=https://eu.i.posthog.comPostHog defaults to the EU instance (eu.i.posthog.com). Change POSTHOG_HOST to https://us.i.posthog.com for the US instance or your self-hosted URL.
Tracking events
Import posthog from the lib and call capture:
import { posthog } from "@/lib/posthog";
// Track a custom event
posthog.capture("feature_used", {
feature: "export",
format: "pdf",
});
// Identify the user after login
posthog.identify(user.id, {
email: user.email,
name: user.name,
});
// Reset on logout
posthog.reset();Use the typed helper functions from lib/analytics.ts:
import { trackEvent, identifyUser, resetUser, trackScreen } from "@/lib/analytics";
// Track a custom event
trackEvent("feature_used", {
feature: "export",
format: "pdf",
});
// Identify the user after login
identifyUser(user.id, {
email: user.email,
name: user.name,
});
// Track screen views
trackScreen("Settings");
// Reset on logout
resetUser();Feature flags
PostHog feature flags work on both platforms:
import { posthog } from "@/lib/posthog";
// Check a boolean flag
if (posthog.isFeatureEnabled("new-dashboard")) {
// Show new dashboard
}
// Get a multivariate flag value
const variant = posthog.getFeatureFlag("pricing-experiment");
if (variant === "variant-a") {
// Show variant A pricing
}
// React hook pattern
import { useFeatureFlagEnabled } from "posthog-js/react";
const MyComponent = () => {
const showBeta = useFeatureFlagEnabled("beta-features");
if (!showBeta) return null;
return <BetaFeature />;
};import { getPostHog } from "@/lib/posthog";
// Check a boolean flag
const isEnabled = await getPostHog()?.isFeatureEnabled("new-onboarding");
// Get a multivariate flag value
const variant = await getPostHog()?.getFeatureFlag("pricing-experiment");Session replay (Web only)
PostHog session replay records user sessions for debugging. Enable it in PostHog initialization:
export const initPostHog = () => {
if (typeof window !== "undefined" && process.env.NEXT_PUBLIC_POSTHOG_KEY) {
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://eu.i.posthog.com",
session_recording: {
recordCrossOriginIframes: true,
},
capture_pageview: true,
capture_pageleave: true,
});
}
};Session replay must also be enabled in your PostHog project settings. The SDK flag alone is not enough.
When analytics is generated
Analytics is conditionally generated based on your platform selection:
| Platform selected | PostHog generated |
|---|---|
| Web only | posthog-js + provider |
| Mobile only | posthog-react-native + helpers |
| Web + Mobile | Both |
| API only | No analytics generated |
| Desktop | Inherits web analytics (shares Next.js frontend) |
The analytics generator runs after the platform generators, patching existing layout files to add the PostHog initialization.