# Analytics Tracker Typed event tracking for Next.js 14 — PostHog wrapper with auto page view hook, user identification, a self-hosted Supabase fallback, and six pre-written SQL queries for DAU, WAU, funnel, revenue, and retention cohorts. ## What's included **PostHog wrapper** - `initAnalytics(config)` — initialises PostHog with lazy import; disables autocapture and built-in pageview tracking; accepts `posthogKey`, `apiHost`, `debug`, `disabled` - `track(event, properties?)` — captures a typed `AnalyticsEvent` with an auto-added `timestamp`; logs to console in development; no-ops silently if PostHog isn't loaded - `identifyUser(userId, traits?)` — calls `posthog.identify`; pass after login/session restore - `resetAnalytics()` — calls `posthog.reset`; call on logout to disassociate the device - `trackPageView(url?)` — manual page view capture; defaults to `window.location.pathname` - `setUserProperty(key, value)` — sets a PostHog person property via `people.set` **React hooks** - `usePageTracking()` — auto-fires `trackPageView` on `usePathname` changes; deduplicates consecutive fires; drop into a layout component - `useTrackOnce(event, props?)` — fires a single event on mount; useful for impression tracking on detail pages **Self-hosted Supabase fallback** - `trackToSupabase(event, userId, properties?)` — POSTs to `/api/analytics`; includes session ID (from `sessionStorage`), URL, and referrer; use when PostHog is unavailable or for private data - `ANALYTICS_API_ROUTE` — paste-ready content for `app/api/analytics/route.ts`; inserts into `analytics_events` via service role **SQL queries** - `ANALYTICS_QUERIES.dailyActiveUsers` — DAU for the last 30 days - `ANALYTICS_QUERIES.weeklyActiveUsers` — WAU for the last 90 days - `ANALYTICS_QUERIES.topEvents` — top 15 events by count in the last 7 days - `ANALYTICS_QUERIES.conversionFunnel` — page view → detail view → purchase started → purchase completed counts for the last 30 days - `ANALYTICS_QUERIES.revenueByEvent` — revenue and purchase count per `block_id` from `purchase_completed` events - `ANALYTICS_QUERIES.retentionCohort` — weekly retention cohort table by signup week **Types** - `AnalyticsEvent` — union of 21 typed event names - `EventProperties` — `Record` ## Setup ### 1. Install dependencies ```bash npm install posthog-js ``` ### 2. Environment variables ``` NEXT_PUBLIC_POSTHOG_KEY=phc_xxxxxxxxxxxxxxxxxxxx # PostHog project API key NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com # or your self-hosted host # Self-hosted fallback only: NEXT_PUBLIC_SUPABASE_URL=your Supabase project URL SUPABASE_SERVICE_ROLE_KEY=service role key (server-only) ``` ### 3. Initialise in your providers ```tsx // app/providers.tsx 'use client' import { useEffect } from 'react' import { initAnalytics, usePageTracking } from '@/blocks/analytics' function PageTracker() { usePageTracking(); return null } export function Providers({ children }: { children: React.ReactNode }) { useEffect(() => { initAnalytics({ posthogKey: process.env.NEXT_PUBLIC_POSTHOG_KEY! }) }, []) return <>{children} } ``` ### 4. Self-hosted fallback (optional) Copy `ANALYTICS_API_ROUTE` to `app/api/analytics/route.ts`, then run this in Supabase: ```sql CREATE TABLE analytics_events ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES profiles(id) ON DELETE SET NULL, session_id TEXT NOT NULL, event TEXT NOT NULL, properties JSONB NOT NULL DEFAULT '{}', url TEXT, referrer TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX analytics_event_type_idx ON analytics_events(event, created_at DESC); CREATE INDEX analytics_user_idx ON analytics_events(user_id, created_at DESC); ``` ## Usage examples ```ts // Track events anywhere — server or client import { track } from '@/blocks/analytics' // After a successful purchase track('purchase_completed', { block_id: 'auth', amount: 19, currency: 'USD' }) // After a failed purchase track('purchase_failed', { block_id: 'auth', reason: 'card_declined' }) ``` ```ts // Identify user after login (call once per session) import { identifyUser, resetAnalytics } from '@/blocks/analytics' // On sign-in: identifyUser(session.user.id, { email: session.user.email, role: session.user.role, plan: 'pro', createdAt: session.user.createdAt, }) // On sign-out: resetAnalytics() ``` ```tsx // Track a block detail view once on mount 'use client' import { useTrackOnce } from '@/blocks/analytics' export function BlockDetailPage({ block }) { useTrackOnce('block_detail_viewed', { block_id: block.id, price: block.price }) return
{/* content */}
} ``` ## Notes - `initAnalytics` uses a dynamic `import('posthog-js')` — PostHog is not loaded until `initAnalytics` is called, so events fired before the async import resolves are silently dropped; call `initAnalytics` as early as possible (top of `providers.tsx`) - `track` is a no-op on the server — the `_posthog` module variable is always `null` in RSC/API routes; use `trackToSupabase` for server-side event capture - `ANALYTICS_QUERIES` are plain SQL strings — run them via `db.rpc`, a Supabase SQL editor, or a migration; they reference the `analytics_events` table and the `properties` JSONB column directly, so they only work with the self-hosted fallback, not with PostHog - `conversionFunnel` counts events across all users for the period, not per-user funnel steps — a user who viewed a detail page 10 times counts as 10; for true per-user funnel analysis, use PostHog's built-in funnel charts