Skip to main content

How to Add Analytics Events

This guide covers adding custom analytics events using PostHog for product analytics and Sentry for error tracking. The template provides a unified Analytics class that manages both providers through a single API.

Prerequisites

  • PostHog project set up with a valid API key
  • Sentry project configured (optional, for error tracking)
  • Understanding of client-side vs. server-side event contexts

Architecture Overview

Analytics in the template is managed by a singleton Analytics class in lib/analytics/index.ts:

lib/analytics/
index.ts <-- Analytics class (PostHog + Sentry)

lib/services/
posthog-api.service.ts <-- Server-side PostHog API client

lib/constants.ts <-- Configuration flags (POSTHOG_KEY, SENTRY_DSN, etc.)

The Analytics class handles:

  • Event tracking via PostHog (analytics.track())
  • Page view tracking (analytics.trackPageView())
  • User identification (analytics.identify())
  • Feature flags (analytics.isFeatureEnabled())
  • Exception tracking via PostHog and/or Sentry (analytics.captureException())

Step 1: Track a Custom Event (Client-Side)

Import the Analytics Instance

import { analytics } from "@/lib/analytics";

Track an Event

// Basic event
analytics.track("item_bookmarked", {
itemSlug: "my-tool",
category: "productivity",
});

// Event with user context (after identify)
analytics.track("search_performed", {
query: searchTerm,
resultCount: results.length,
filters: activeFilters,
});

Common Event Patterns

Event NameWhen to FireProperties
item_viewedUser views an item detail pageitemSlug, category, source
item_bookmarkedUser bookmarks an itemitemSlug, category
search_performedUser executes a searchquery, resultCount, filters
filter_appliedUser applies a filterfilterType, filterValue
signup_completedUser completes registrationmethod (email, Google, GitHub)
subscription_startedUser starts a paid planplanName, billingPeriod

Step 2: Identify Users

Call identify after the user logs in to associate events with their profile:

// After successful authentication
analytics.identify(session.user.id, {
email: session.user.email,
name: session.user.name,
plan: userProfile.plan,
accountType: userProfile.accountType,
});

Set additional user properties at any time:

analytics.setUserProperties({
plan: "premium",
totalItems: 42,
lastLoginAt: new Date().toISOString(),
});

Reset the identity on logout:

analytics.reset();

Step 3: Track Page Views

The template can auto-capture page views (controlled by POSTHOG_AUTO_CAPTURE), but you can also track them manually for SPA navigation:

// In a layout or navigation component
analytics.trackPageView(window.location.href, {
locale: currentLocale,
section: "admin",
});

Step 4: Track Errors and Exceptions

The Analytics class provides unified error tracking that routes to PostHog, Sentry, or both based on configuration:

try {
await riskyOperation();
} catch (error) {
// Track in PostHog and/or Sentry
analytics.captureException(error, {
component: "BookmarkButton",
action: "create_bookmark",
itemSlug: slug,
});
}

Server-Side Error Tracking

For server-side code (API routes, services), use Sentry directly:

import * as Sentry from "@sentry/nextjs";

try {
await databaseOperation();
} catch (error) {
Sentry.captureException(error, {
tags: {
component: "BookmarkService",
operation: "create",
},
extra: {
userId,
itemSlug,
},
});
throw error; // Re-throw or handle as appropriate
}

Step 5: Use Feature Flags

PostHog feature flags allow you to roll out features gradually:

// Check a feature flag
const showNewSearch = analytics.isFeatureEnabled(
"new-search-ui",
false // default value
);

if (showNewSearch) {
// Render new search component
}

// Reload flags (e.g., after user property changes)
await analytics.reloadFeatureFlags();

Step 6: Set Super Properties

Super properties are sent with every subsequent event -- useful for global context:

analytics.setSuperProperties({
appVersion: "2.1.0",
theme: currentTheme,
locale: currentLocale,
});

Server-Side Analytics with PostHog API

For server-side analytics (e.g., in cron jobs or API routes), use the PostHogApiService:

import { PostHogApiService } from "@/lib/services/posthog-api.service";

const posthogApi = new PostHogApiService();

// Get total page views for the last 30 days
const pageViews = await posthogApi.getTotalPageViews(30);

This service makes authenticated API calls to the PostHog API and is used by the admin analytics dashboard.


Configuration Reference

Analytics behavior is controlled by environment variables:

VariableDescriptionDefault
NEXT_PUBLIC_POSTHOG_KEYPostHog project API key--
NEXT_PUBLIC_POSTHOG_HOSTPostHog instance URLhttps://us.i.posthog.com
NEXT_PUBLIC_POSTHOG_DEBUGEnable debug loggingfalse
NEXT_PUBLIC_POSTHOG_SESSION_RECORDINGEnable session recordingfalse
NEXT_PUBLIC_POSTHOG_AUTO_CAPTUREAuto-capture page viewstrue
NEXT_PUBLIC_POSTHOG_SAMPLE_RATEEvent sampling rate (0-1)1
NEXT_PUBLIC_POSTHOG_EXCEPTION_TRACKINGTrack exceptions in PostHogfalse
NEXT_PUBLIC_SENTRY_DSNSentry DSN for error tracking--
NEXT_PUBLIC_SENTRY_DEBUGEnable Sentry debug modefalse
NEXT_PUBLIC_EXCEPTION_TRACKING_PROVIDERProvider: posthog, sentry, both, nonesentry

Event Naming Conventions

Follow these conventions for consistent analytics:

ConventionExample
Use snake_caseitem_bookmarked, not itemBookmarked
Start with the objectitem_viewed, user_signed_up
Use past tense for completed actionssearch_performed, filter_applied
Prefix internal events with $$pageview, $exception (PostHog convention)
Include relevant entity IDs{ itemSlug, userId, category }

Common Pitfalls

PitfallSolution
Events not appearing in PostHogCheck that NEXT_PUBLIC_POSTHOG_KEY is set and analytics.init() is called
Tracking events on the serverThe Analytics class is client-only; use PostHogApiService or Sentry for server-side tracking
Too many events overwhelming PostHog quotaUse sampling (POSTHOG_SAMPLE_RATE) and be selective about which events to track
Feature flags returning defaultsEnsure analytics.init() is called before checking flags; flags are loaded asynchronously
Sentry errors not linking to PostHogSet EXCEPTION_TRACKING_PROVIDER=both to send errors to both providers
PII leaking into analyticsNever include passwords, tokens, or full email addresses in event properties