Creating a Custom Analytics Provider
Integrate Google Analytics, Mixpanel, PostHog, or any analytics service by implementing the AnalyticsService interface.
How to create a custom analytics provider
Add your preferred analytics service to the kit.
The analytics package is designed for extensibility. You implement the AnalyticsService interface, register your provider, and the AnalyticsManager handles dispatching events to all registered providers.
Implement the AnalyticsService Interface
Create a new file in the analytics package:
packages/analytics/src/google-analytics-service.ts
import { AnalyticsService } from './types';export class GoogleAnalyticsService implements AnalyticsService { private measurementId: string; constructor(config?: { measurementId?: string }) { this.measurementId = config?.measurementId ?? ''; } async initialize() { // Load the GA4 script if (typeof window === 'undefined') return; const script = document.createElement('script'); script.src = `https://www.googletagmanager.com/gtag/js?id=${this.measurementId}`; script.async = true; document.head.appendChild(script); window.dataLayer = window.dataLayer || []; window.gtag = function gtag() { window.dataLayer.push(arguments); }; window.gtag('js', new Date()); window.gtag('config', this.measurementId); } async identify(userId: string, traits?: Record<string, string>) { if (typeof window === 'undefined') return; window.gtag('config', this.measurementId, { user_id: userId, ...traits, }); } async trackPageView(path: string) { if (typeof window === 'undefined') return; window.gtag('event', 'page_view', { page_path: path, }); } async trackEvent( eventName: string, eventProperties?: Record<string, string | string[]> ) { if (typeof window === 'undefined') return; window.gtag('event', eventName, eventProperties); }}// Type declarations for gtagdeclare global { interface Window { dataLayer: unknown[]; gtag: (...args: unknown[]) => void; }}Register the Provider
Update the analytics index file to include your provider:
packages/analytics/src/index.ts
import { createAnalyticsManager } from './analytics-manager';import { GoogleAnalyticsService } from './google-analytics-service';import { NullAnalyticsService } from './null-analytics-service';import type { AnalyticsManager } from './types';export const analytics: AnalyticsManager = createAnalyticsManager({ providers: { google: () => new GoogleAnalyticsService({ measurementId: process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID, }), null: () => NullAnalyticsService, },});Multiple Providers
You can register multiple providers to send events to several services simultaneously:
packages/analytics/src/index.ts
import { createAnalyticsManager } from './analytics-manager';import { GoogleAnalyticsService } from './google-analytics-service';import { MixpanelService } from './mixpanel-service';import { PostHogService } from './posthog-service';import { NullAnalyticsService } from './null-analytics-service';import type { AnalyticsManager } from './types';export const analytics: AnalyticsManager = createAnalyticsManager({ providers: { google: () => new GoogleAnalyticsService({ measurementId: process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID, }), mixpanel: () => new MixpanelService({ token: process.env.NEXT_PUBLIC_MIXPANEL_TOKEN, }), posthog: () => new PostHogService({ apiKey: process.env.NEXT_PUBLIC_POSTHOG_KEY, host: process.env.NEXT_PUBLIC_POSTHOG_HOST, }), null: () => NullAnalyticsService, },});When you call analytics.trackEvent(), the event is sent to Google Analytics, Mixpanel, and PostHog simultaneously.
Server-Side Tracking
For server-side analytics, update the server export as well:
packages/analytics/src/server.ts
import 'server-only';import { createAnalyticsManager } from './analytics-manager';import { ServerAnalyticsService } from './server-analytics-service';import { NullAnalyticsService } from './null-analytics-service';import type { AnalyticsManager } from './types';export const analytics: AnalyticsManager = createAnalyticsManager({ providers: { server: () => new ServerAnalyticsService({ apiKey: process.env.ANALYTICS_API_KEY, }), null: () => NullAnalyticsService, },});Server-side providers typically use HTTP APIs instead of browser scripts:
packages/analytics/src/server-analytics-service.ts
import { AnalyticsService } from './types';export class ServerAnalyticsService implements AnalyticsService { private apiKey: string; private endpoint: string; constructor(config?: { apiKey?: string; endpoint?: string }) { this.apiKey = config?.apiKey ?? ''; this.endpoint = config?.endpoint ?? 'https://api.analytics.example.com'; } async initialize() { // Server-side services typically don't need initialization } async identify(userId: string, traits?: Record<string, string>) { await fetch(`${this.endpoint}/identify`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.apiKey}`, }, body: JSON.stringify({ userId, traits }), }); } async trackPageView(path: string) { await fetch(`${this.endpoint}/page`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.apiKey}`, }, body: JSON.stringify({ path }), }); } async trackEvent( eventName: string, eventProperties?: Record<string, string | string[]> ) { await fetch(`${this.endpoint}/track`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.apiKey}`, }, body: JSON.stringify({ event: eventName, properties: eventProperties }), }); }}Environment Variables
Add your analytics credentials to your environment files:
apps/web/.env.local
# Google AnalyticsNEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX# MixpanelNEXT_PUBLIC_MIXPANEL_TOKEN=your-mixpanel-token# PostHogNEXT_PUBLIC_POSTHOG_KEY=phc_xxxxxxxxxxxxxNEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com# Server-side analyticsANALYTICS_API_KEY=your-server-api-keyConditional Provider Loading
To load providers based on environment:
packages/analytics/src/index.ts
import { createAnalyticsManager } from './analytics-manager';import { NullAnalyticsService } from './null-analytics-service';import type { AnalyticsManager, AnalyticsProviderFactory } from './types';const providers: Record<string, AnalyticsProviderFactory<object>> = { null: () => NullAnalyticsService,};// Only add Google Analytics if configuredif (process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID) { const { GoogleAnalyticsService } = require('./google-analytics-service'); providers.google = () => new GoogleAnalyticsService({ measurementId: process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID, });}export const analytics: AnalyticsManager = createAnalyticsManager({ providers,});Common Gotchas
- Browser-only code - Always check
typeof window !== 'undefined'before accessing browser APIs in your service. - Async initialization - The
initialize()method is called immediately when the provider is registered. If you load external scripts, they may not be ready when the first tracking call happens. - Event property types - The interface expects
Record<string, string | string[]>. If your analytics service needs other types, convert them in your implementation. - Server imports - Don't import client providers in server files. Keep separate provider configurations for client (
index.ts) and server (server.ts).
Previous: Analytics Overview ←