Skip to main content

Analytics

Three analytics systems — built-in, Google Analytics 4, PostHog. When to use each, how to combine them, and the privacy trade-offs.

Why this matters

Analytics in a directory is the difference between guessing and knowing. You need three things to operate: which categories drive traffic (so you double down on content there), where users drop in the submission funnel (so you fix UX), and which projects get clicked through (so you justify premium pricing). One tool rarely covers all three well — that's why DirectoryLaunch ships three layered systems and lets you mix and match.

This page maps which system answers which question, what each costs (in money and in cookies), and how to keep the data clean enough to drive decisions. If you only enable one, enable the built-in analytics — it's free, owns its own data, and powers the admin dashboard you'll actually open.

DirectoryKit records usage in up to three places at once. Turn on whichever you need.

SystemData homeWhat it's best for
Built-in enhanced analyticsYour Supabase DBFast admin charts, no cookies
Google Analytics 4GoogleSEO reports, acquisition
PostHogPostHog cloudFunnels, session replay, feature flags

Setup for GA and PostHog is in Analytics Config.

Built-in analytics

Requires the analytics feature flag (on by default).

What gets tracked

EventWhere
Page viewsEvery page
Project views/project/[slug]
Category views/categories/[slug]
Submission funnel steps/submit
Click-outs (visits to project URLs)Project cards
Device, browser, OSAll of the above
Country (from IP)All of the above

Events are written to the analytics table via lib/analytics.ts. No personally identifying data — no IPs stored, no emails, no names.

Admin dashboard

Charts come from Recharts. Filters: date range, event type, project (drill-down).

Use it in your code

import { trackEvent } from '@/lib/analytics';
 
await trackEvent('project_clicked', {
  project_id: app.id,
  source: 'homepage',
});

Google Analytics 4

Set NEXT_PUBLIC_GA_MEASUREMENT_ID in .env.local. The tag auto-injects into <head>. See Analytics Config for step-by-step.

Events sent to GA:

  • Page views (auto)
  • project_viewed, project_clicked, submission_started, submission_completed

PostHog

Set NEXT_PUBLIC_POSTHOG_KEY and NEXT_PUBLIC_POSTHOG_HOST. The SDK initializes on page load.

Good for:

  • Funnel: /submit step drop-off
  • Session replay on premium submits
  • A/B tests via feature flags
  • Per-user cohorts

Turning it all off

Disable GA / PostHog by removing their env vars. Disable the built-in tracker with analytics: false in features.config.ts.

Privacy note

The built-in tracker stores no PII by default. If you want to comply with GDPR-style consent:

  1. Use a cookie banner component
  2. Check consent before calling trackEvent()
  3. Flip the SiteTracker provider to respect the consent cookie

The code is intentionally simple — you own the data, you decide the policy.

A real-world walkthrough: which system answers which question

Six months into running a directory, you'll ask yourself recurring questions. Here's where to look:

  • "Which categories grew fastest last month?" → Built-in analytics → /admin/analytics → filter by event_type=category_view. Your DB already has this.
  • "What % of submission starts complete?" → PostHog funnel: submission_startedsubmission_completed. PostHog visualises this in seconds; recreating it in Recharts is a half-day project.
  • "Which Google search terms drive traffic?" → Google Search Console (not GA4 directly), linked to GA4. Search-term-level data only exists in GSC.
  • "Why did one project spike yesterday?" → PostHog session replay (filter to that project's URL). Built-in analytics can tell you it spiked; only PostHog can show you why (a Hacker News referrer, a broken page, etc.).
  • "How does mobile vs desktop conversion compare?" → GA4 explorations or PostHog cohorts. Built-in tracker stores device but doesn't slice it by funnel stage.

The implication: ship built-in always, GA4 if you care about SEO acquisition, PostHog if you care about product behaviour. Don't enable all three "just in case" — three trackers triple the privacy surface and slow page loads measurably.

Common pitfalls

  • Letting all three trackers fire simultaneously without consent gating. EU users on a non-consented page get tagged by GA, PostHog and the built-in tracker — all three become liabilities under GDPR. Wire the consent cookie check around every trackEvent call and the GA/PostHog providers.
  • Comparing GA4 numbers to built-in numbers. They will never match. GA4 sampling, ad-blockers, and consent denials shave 20–40% off GA4's count. Pick one as your source of truth (we recommend built-in) and document the discrepancy.
  • Storing PII in the built-in tracker. Tempting to log email or user_id "for richer analytics", but it turns your analytics table into a GDPR risk. Stick to anonymised dimensions — country, device, referrer, event_type.
  • Tracking too many event types. Beyond ~20 event types, the admin dashboard becomes unreadable and the table grows fast. Cull events you haven't queried in 90 days.
  • Forgetting to rotate the GA4 measurement ID for staging. A staging deploy with the production GA ID pollutes your real reports with test traffic. Use NEXT_PUBLIC_GA_MEASUREMENT_ID differently per environment in Vercel.

FAQ

Will the built-in analytics scale to 1M pageviews/month?

Yes, with two caveats: partition the analytics table by month (Postgres declarative partitioning), and aggregate into a daily_summary table for the admin dashboard so charts query summary rows instead of raw events. Both are 30-minute migrations when you cross 100K events/day.

Can I export analytics data to a warehouse (BigQuery, Snowflake)?

Yes — the analytics table is plain Postgres. Use Supabase's pg_cron or a daily Vercel cron that exports new rows to a CSV in S3 / GCS. Most operators don't need this until they cross 1M events/month.

How do I track outbound clicks accurately when GA blocks them?

GA4 blocks third-party tracking on link clicks by default in some browsers. The built-in tracker uses a server-side endpoint (POST /api/track/click) that fires before the navigation, which sidesteps the issue. Always rely on built-in for click-through analytics; GA is a secondary signal.