AnalyticsBeginner~1 hour

Add analytics

You've shipped an app but you're flying blind — you don't know which pages people visit, where they drop off, or whether anyone actually uses the feature you just built. Analytics turns "I think" into "I know" so you stop guessing about what to build next.

the approach

Analytics comes in two layers and you probably want both. Web analytics answers "how much traffic and from where" (pageviews, referrers, countries). Product analytics answers "what do people DO" (they clicked upgrade, started checkout, abandoned). For most vibe-coded web apps in 2026 the pragmatic default is PostHog for product analytics (events, funnels, session replay, and feature flags in one tool) plus a lightweight web-analytics layer like Vercel Web Analytics or Plausible if you just want clean traffic numbers. The core discipline is NOT the SDK — it's deciding the handful of questions you want answered first, then instrumenting a small, well-named set of events. Ten good events beat two hundred noisy ones.

Step by step

  1. 1

    Write the questions before touching code

    List 5-10 concrete questions you want answered: 'How many people who sign up start a project in the first day?' 'Which feature gets used most?' 'Where does checkout drop off?' Every event you add later should map to one of these. If it doesn't answer a question, don't track it.

  2. 2

    Pick your layer(s)

    Just want traffic numbers with zero config? Turn on Vercel Web Analytics (one component) or add Plausible (privacy-friendly, no cookie banner). Want funnels, retention, and 'who did what'? Use PostHog. Don't bolt on Google Analytics 4 too unless someone specifically needs it — running two product-analytics tools doubles the maintenance for the same answers.

  3. 3

    Install and initialize the SDK once

    Add the client at the app root (a Providers component in Next.js App Router) so it loads on every page. Put your project key in an env var like NEXT_PUBLIC_POSTHOG_KEY. Use the SDK's modern defaults so autocapture and SPA-aware pageviews just work — don't hand-roll pageview tracking on every route.

  4. 4

    Reverse-proxy the ingest endpoint

    Ad-blockers and Safari's tracker blocking silently eat requests to analytics domains, so you lose 20-40% of events. Route ingestion through your own domain (e.g. a /ingest rewrite in next.config) so events look first-party. This one step is the difference between trustworthy numbers and mysteriously low ones.

  5. 5

    Define a small event taxonomy

    Create one events.ts file with your event names as constants: 'signup_completed', 'project_created', 'checkout_started', 'checkout_completed'. Pick a convention (snake_case, object_action or noun_verb) and never deviate. Inconsistent names like 'Checkout Started' vs 'checkout-started' fragment your funnels and can't be un-mixed later.

  6. 6

    Identify users so events tie to accounts

    After login, call identify with a stable user id (never an email as the id) and set a few person properties like plan or signup_date. This links anonymous pre-login activity to the real account and unlocks retention/cohort analysis. On logout, reset so the next person on a shared device isn't merged in.

  7. 7

    Handle consent and keep PII out

    If you have EU users, gate loading (or at least identify) behind a consent choice, or use a cookieless mode. Never pass raw PII — full names, emails, card numbers — as event properties; send ids and categories instead. Leaking PII into a third-party analytics tool is a real compliance problem, not a nit.

  8. 8

    Verify events actually land

    Open your app, do the key actions, and watch them appear in PostHog's live-events / activity view (or the network tab hitting your /ingest proxy). Confirm names and properties match your constants exactly. Then build one funnel from your question list so you know the data is usable, not just arriving.

Starter snippet

tsx
// app/providers.tsx — PostHog in a Next.js App Router app
'use client'
import posthog from 'posthog-js'
import { PostHogProvider } from 'posthog-js/react'
import { useEffect } from 'react'

export function Providers({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
      api_host: '/ingest',              // reverse-proxy → dodges ad-blockers
      ui_host: 'https://us.posthog.com',
      defaults: '2025-05-24',          // modern SPA pageviews + autocapture
      person_profiles: 'identified_only', // cheaper: only profile logged-in users
    })
  }, [])
  return <PostHogProvider client={posthog}>{children}</PostHogProvider>
}

// Anywhere in a client component — fire a named event from your constants:
// import { EVENTS } from '@/lib/events'
// posthog.capture(EVENTS.CHECKOUT_STARTED, { plan: 'pro' })
//
// After login, tie activity to the account:
// posthog.identify(user.id, { plan: user.plan })  // id, NOT email

✕ Watch out for

  • Event soup: tracking everything 'just in case' so no one can find the 5 events that matter. Start narrow and add events when a question demands them.
  • Inconsistent naming ('Signup Completed' vs 'signup_completed' vs 'user-signed-up') — these become separate events that can't be merged, silently breaking your funnels.
  • Ad-blockers eating 20-40% of events because you called the analytics domain directly. Without a first-party reverse proxy your numbers are quietly wrong, not missing.
  • Leaking PII (emails, names, raw addresses) into event properties or using an email as the identify id — a privacy/compliance problem and it makes ids unstable.
  • Never verifying. The SDK loads with no error, you assume it works, and three weeks later you discover the key was wrong or events never fired in production.

✓ Pro tips

  • Hand your AI agent the event taxonomy as an explicit table before it writes any code: event name, when it fires, and its properties. Then tell it 'only implement these events, do not invent new ones, and put the names in a single events.ts constants file.' Afterward, grep the diff for capture( calls and confirm it didn't add stray events or rename yours.
  • Ask the agent to import event names from constants everywhere — never inline string literals. This is the single biggest lever against naming drift, and it's easy to check in review.
  • Test in a real browser with an ad-blocker ON, watching the network tab and the tool's live-events view. If events survive the ad-blocker, your proxy setup is correct.
  • Keep a running one-page doc of every event and what it means. Future-you (and your agent) will add duplicates without it.