Analytics
beginner*Privacy-friendly analytics with PostHog. Includes event tracking, user identification, and feature flag integration.
analyticsposthogtrackingeventsprivacy
Tested on⬢20▲16⚛19TS5.9
$ bunx sinew add developer-experience/analyticsInteractive demo coming soon
1The Problem
Tracking user behavior requires:
- Privacy-respecting data collection
- Custom event tracking
- User identification
- Server-side tracking support
2The Solution
Use PostHog for product analytics with event tracking, user identification, and feature flag integration. Includes both client-side and server-side tracking utilities.
3Files
lib/analytics/client.ts
lib/analytics/client.tsTypeScript
import posthog from "posthog-js";
// Module-level guard so we don't re-init on Fast Refresh or repeated mounts.
let hasInitialized = false;
// Initialize PostHog (call once in your app)
export function initAnalytics() {
if (typeof window === "undefined") return;
if (hasInitialized) return;
hasInitialized = true;
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
// US cloud. Use https://eu.i.posthog.com for the EU region.
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://us.i.posthog.com",
capture_pageview: false, // We'll capture manually
capture_pageleave: true,
persistence: "localStorage+cookie",
});
}
// Identify user (call after login)
export function identifyUser(userId: string, properties?: Record<string, unknown>) {
posthog.identify(userId, properties);
}
// Reset user (call after logout)
export function resetUser() {
posthog.reset();
}
// Track page view
export function trackPageView(url?: string) {
posthog.capture("$pageview", {
$current_url: url || window.location.href,
});
}
// Track custom event
export function trackEvent(event: string, properties?: Record<string, unknown>) {
posthog.capture(event, properties);
}lib/analytics/events.ts
lib/analytics/events.tsTypeScript
import { trackEvent } from "./client";
// Type-safe event tracking
export const Events = {
// User events
signUp: (method: "email" | "google" | "github") => trackEvent("user_signed_up", { method }),
login: (method: "email" | "google" | "github") => trackEvent("user_logged_in", { method }),
// Conversion events
trialStarted: (plan: string) => trackEvent("trial_started", { plan }),
subscriptionStarted: (plan: string, interval: "monthly" | "yearly") =>
trackEvent("subscription_started", { plan, interval }),
// E-commerce events
purchaseCompleted: (orderId: string, total: number, items: number) =>
trackEvent("purchase_completed", { orderId, total, items }),
// Engagement events
searchPerformed: (query: string, resultsCount: number) =>
trackEvent("search_performed", { query, resultsCount }),
};components/analytics-provider.tsx
components/analytics-provider.tsxTSX
"use client";
import { useEffect } from "react";
import { usePathname, useSearchParams } from "next/navigation";
import { initAnalytics, trackPageView } from "@/lib/analytics/client";
export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
const searchParams = useSearchParams();
// Initialize PostHog on mount
useEffect(() => {
initAnalytics();
}, []);
// Track page views on route change
useEffect(() => {
if (pathname) {
const url = searchParams.toString() ? `${pathname}?${searchParams.toString()}` : pathname;
trackPageView(url);
}
}, [pathname, searchParams]);
return <>{children}</>;
}lib/analytics/server.ts
lib/analytics/server.tsTypeScript
import { PostHog } from "posthog-node";
let posthogServer: PostHog | null = null;
export function getServerPostHog(): PostHog {
if (!posthogServer) {
posthogServer = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
// US cloud. Use https://eu.i.posthog.com for the EU region.
host: process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://us.i.posthog.com",
flushAt: 1,
flushInterval: 0,
});
}
return posthogServer;
}
// Track server-side event
export async function trackServerEvent(
distinctId: string,
event: string,
properties?: Record<string, unknown>
) {
const posthog = getServerPostHog();
posthog.capture({ distinctId, event, properties });
await posthog.flush();
}4Dependencies
$ bun add posthog-js posthog-node5Configuration
Environment Variables
| Variable | Description | Required |
| -------------------------- | ----------------------- | -------- |
| NEXT_PUBLIC_POSTHOG_KEY | PostHog project API key | Yes |
| NEXT_PUBLIC_POSTHOG_HOST | PostHog host (US or EU) | No |
6Usage
Add Provider to Layout
import { Suspense } from "react";
import { AnalyticsProvider } from "@/components/analytics-provider";
export default function RootLayout({ children }) {
return (
<html>
<body>
<Suspense fallback={null}>
<AnalyticsProvider>{children}</AnalyticsProvider>
</Suspense>
</body>
</html>
);
}TSX
Track Events
import { Events } from "@/lib/analytics/events";
// On sign up
Events.signUp("google");
// On purchase
Events.purchaseCompleted("order_123", 99.99, 3);TypeScript
7Alternatives
- PostHog - Open-source with feature flags
- Plausible - Privacy-focused, no cookies
- Umami - Open-source, self-hostable
- Mixpanel - Advanced funnel analysis