| name | analytics-tracking |
| description | Expert guide for tracking user analytics, events, conversions, A/B testing, and data-driven insights. Use when implementing analytics, tracking user behavior, or optimizing conversions. |
Analytics & Tracking Skill
Overview
This skill helps you implement comprehensive analytics and event tracking in your Next.js application. From basic page views to complex conversion funnels, this covers everything you need for data-driven decisions.
Analytics Providers
Google Analytics 4 (GA4)
Install:
npm install @next/third-parties
Setup:
// app/layout.tsx
import { GoogleAnalytics } from '@next/third-parties/google'
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<GoogleAnalytics gaId="G-XXXXXXXXXX" />
</body>
</html>
)
}
Track Events:
'use client'
export function TrackableButton() {
const handleClick = () => {
// Track custom event
window.gtag('event', 'button_click', {
event_category: 'engagement',
event_label: 'cta_button',
value: 1
})
}
return <button onClick={handleClick}>Click Me</button>
}
Vercel Analytics
Install:
npm install @vercel/analytics
Setup:
// app/layout.tsx
import { Analytics } from '@vercel/analytics/react'
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Analytics />
</body>
</html>
)
}
Track Custom Events:
import { track } from '@vercel/analytics'
// Track event
track('Purchase', { amount: 99.99, currency: 'USD' })
PostHog (Open Source)
Install:
npm install posthog-js
Setup:
// lib/posthog.ts
import posthog from 'posthog-js'
export function initPostHog() {
if (typeof window !== 'undefined') {
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://app.posthog.com',
loaded: (posthog) => {
if (process.env.NODE_ENV === 'development') posthog.debug()
}
})
}
}
// app/providers.tsx
'use client'
import { useEffect } from 'react'
import { initPostHog } from '@/lib/posthog'
export function Providers({ children }) {
useEffect(() => {
initPostHog()
}, [])
return <>{children}</>
}
Track Events:
import posthog from 'posthog-js'
posthog.capture('user_signed_up', {
plan: 'pro',
source: 'landing_page'
})
Custom Analytics System
Event Tracking Hook
// hooks/use-analytics.ts
'use client'
import { useCallback } from 'react'
type EventProperties = Record<string, any>
export function useAnalytics() {
const track = useCallback((eventName: string, properties?: EventProperties) => {
// Send to multiple providers
if (typeof window !== 'undefined') {
// Google Analytics
window.gtag?.('event', eventName, properties)
// PostHog
window.posthog?.capture(eventName, properties)
// Custom backend
fetch('/api/analytics/track', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
event: eventName,
properties,
timestamp: new Date().toISOString(),
url: window.location.href,
referrer: document.referrer,
})
}).catch(() => {}) // Fail silently
}
}, [])
const identify = useCallback((userId: string, traits?: EventProperties) => {
if (typeof window !== 'undefined') {
window.gtag?.('config', 'GA_MEASUREMENT_ID', { user_id: userId })
window.posthog?.identify(userId, traits)
}
}, [])
const page = useCallback((pageName: string, properties?: EventProperties) => {
if (typeof window !== 'undefined') {
window.gtag?.('event', 'page_view', {
page_title: pageName,
...properties
})
window.posthog?.capture('$pageview', properties)
}
}, [])
return { track, identify, page }
}
// Usage
function Component() {
const { track } = useAnalytics()
const handlePurchase = () => {
track('Purchase Completed', {
amount: 99.99,
currency: 'USD',
product: 'Pro Plan'
})
}
return <button onClick={handlePurchase}>Buy Now</button>
}
Page View Tracking
// app/providers.tsx
'use client'
import { useEffect } from 'react'
import { usePathname, useSearchParams } from 'next/navigation'
import { useAnalytics } from '@/hooks/use-analytics'
export function AnalyticsProvider({ children }) {
const pathname = usePathname()
const searchParams = useSearchParams()
const { page } = useAnalytics()
useEffect(() => {
page(pathname, {
search: searchParams.toString()
})
}, [pathname, searchParams, page])
return <>{children}</>
}
Event Types
Common Events to Track
User Authentication:
track('User Signed Up', { method: 'email' })
track('User Logged In', { method: 'google' })
track('User Logged Out')
Engagement:
track('Button Clicked', { button_id: 'cta', location: 'hero' })
track('Link Clicked', { url: '/pricing', text: 'See Pricing' })
track('Video Played', { video_id: 'intro', duration: 120 })
track('Form Submitted', { form_id: 'contact', success: true })
E-commerce:
track('Product Viewed', { product_id: '123', name: 'Pro Plan' })
track('Product Added to Cart', { product_id: '123', quantity: 1 })
track('Checkout Started', { cart_total: 99.99 })
track('Order Completed', {
order_id: 'ORD-123',
total: 99.99,
items: 3
})
Errors:
track('Error Occurred', {
error_type: 'payment_failed',
error_message: 'Card declined',
location: 'checkout'
})
Conversion Tracking
Funnel Tracking
// lib/funnel.ts
type FunnelStep =
| 'landing'
| 'signup'
| 'onboarding'
| 'first_action'
| 'activation'
export class FunnelTracker {
private steps: FunnelStep[] = []
trackStep(step: FunnelStep, properties?: Record<string, any>) {
this.steps.push(step)
track('Funnel Step Completed', {
step,
step_number: this.steps.length,
funnel_id: 'user_activation',
...properties
})
// Track drop-off if user hasn't progressed
setTimeout(() => {
if (this.steps[this.steps.length - 1] === step) {
track('Funnel Drop Off', {
at_step: step,
steps_completed: this.steps.length
})
}
}, 60000) // 1 minute timeout
}
}
// Usage
const funnel = new FunnelTracker()
// On landing page
funnel.trackStep('landing')
// After signup
funnel.trackStep('signup', { method: 'email' })
// After onboarding
funnel.trackStep('onboarding', { completed_steps: 3 })
Goal Tracking
// lib/goals.ts
type Goal =
| 'signup'
| 'first_purchase'
| 'referral'
| 'upgrade_to_pro'
export function trackGoal(goal: Goal, value?: number, properties?: Record<string, any>) {
track('Goal Achieved', {
goal,
value,
...properties
})
// Send to backend for revenue tracking
fetch('/api/analytics/goals', {
method: 'POST',
body: JSON.stringify({
goal,
value,
timestamp: new Date().toISOString()
})
})
}
// Usage
trackGoal('first_purchase', 99.99, { plan: 'pro' })
A/B Testing
Simple A/B Test
// lib/ab-test.ts
'use client'
type Variant = 'A' | 'B'
export function useABTest(testName: string): Variant {
const [variant, setVariant] = useState<Variant>('A')
useEffect(() => {
// Get or set variant
const key = `ab_test_${testName}`
let userVariant = localStorage.getItem(key) as Variant
if (!userVariant) {
userVariant = Math.random() > 0.5 ? 'A' : 'B'
localStorage.setItem(key, userVariant)
// Track assignment
track('AB Test Assigned', {
test_name: testName,
variant: userVariant
})
}
setVariant(userVariant)
}, [testName])
return variant
}
// Usage
function PricingPage() {
const variant = useABTest('pricing_layout')
if (variant === 'A') {
return <PricingLayoutA />
}
return <PricingLayoutB />
}
Advanced A/B Testing with PostHog
import { useFeatureFlagEnabled } from 'posthog-js/react'
export function PricingPage() {
const showNewPricing = useFeatureFlagEnabled('new-pricing-layout')
if (showNewPricing) {
return <NewPricingLayout />
}
return <OldPricingLayout />
}
User Properties
Set User Properties
import posthog from 'posthog-js'
// Set properties
posthog.identify('user-123', {
email: 'user@example.com',
plan: 'pro',
signup_date: '2024-01-01',
total_purchases: 5,
lifetime_value: 499.99
})
// Set properties incrementally
posthog.people.set({ plan: 'enterprise' })
posthog.people.set_once({ first_login: new Date() })
posthog.people.increment('page_views')
Cohort Analysis
Track User Cohorts
// lib/cohorts.ts
export function assignCohort(userId: string, signupDate: Date) {
const cohortMonth = signupDate.toISOString().slice(0, 7) // '2024-01'
posthog.identify(userId, {
cohort_month: cohortMonth,
signup_date: signupDate.toISOString()
})
track('User Assigned to Cohort', {
cohort_month: cohortMonth
})
}
// Usage
assignCohort('user-123', new Date('2024-01-15'))
Session Recording
PostHog Session Replay
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
api_host: 'https://app.posthog.com',
session_recording: {
maskAllInputs: true, // Privacy: mask sensitive inputs
maskTextSelector: '[data-sensitive]',
recordCrossOriginIframes: true
}
})
// Optionally trigger on errors
window.addEventListener('error', () => {
posthog.sessionReplayStart()
})
Privacy & Consent
Cookie Consent
// components/cookie-consent.tsx
'use client'
import { useState, useEffect } from 'react'
export function CookieConsent() {
const [showBanner, setShowBanner] = useState(false)
useEffect(() => {
const consent = localStorage.getItem('cookie_consent')
if (!consent) {
setShowBanner(true)
} else if (consent === 'accepted') {
// Initialize analytics
initAnalytics()
}
}, [])
const accept = () => {
localStorage.setItem('cookie_consent', 'accepted')
setShowBanner(false)
initAnalytics()
}
const decline = () => {
localStorage.setItem('cookie_consent', 'declined')
setShowBanner(false)
}
if (!showBanner) return null
return (
<div className="fixed bottom-0 left-0 right-0 bg-gray-900 text-white p-4">
<p>We use cookies to improve your experience.</p>
<button onClick={accept}>Accept</button>
<button onClick={decline}>Decline</button>
</div>
)
}
function initAnalytics() {
// Initialize GA, PostHog, etc.
window.gtag?.('consent', 'update', {
analytics_storage: 'granted'
})
}
GDPR Compliance
// lib/analytics.ts
export function optOut() {
// Disable all tracking
window['ga-disable-GA_MEASUREMENT_ID'] = true
posthog.opt_out_capturing()
localStorage.setItem('analytics_opt_out', 'true')
}
export function optIn() {
window['ga-disable-GA_MEASUREMENT_ID'] = false
posthog.opt_in_capturing()
localStorage.removeItem('analytics_opt_out')
}
// Check opt-out on load
if (localStorage.getItem('analytics_opt_out') === 'true') {
optOut()
}
Analytics Dashboard
Backend Analytics API
// app/api/analytics/stats/route.ts
import { createClient } from '@/lib/supabase/server'
export async function GET() {
const supabase = createClient()
// Get event stats from database
const { data: events } = await supabase
.from('analytics_events')
.select('event_name, count')
.gte('created_at', new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString())
// Get user stats
const { count: totalUsers } = await supabase
.from('users')
.select('*', { count: 'exact', head: true })
const { count: activeUsers } = await supabase
.from('users')
.select('*', { count: 'exact', head: true })
.gte('last_seen', new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString())
return Response.json({
events,
users: {
total: totalUsers,
active_24h: activeUsers
}
})
}
Performance Metrics
Track Performance
'use client'
import { useEffect } from 'react'
export function PerformanceTracker() {
useEffect(() => {
// Track Core Web Vitals
if ('web-vital' in window) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS((metric) => track('Web Vital', { name: 'CLS', value: metric.value }))
getFID((metric) => track('Web Vital', { name: 'FID', value: metric.value }))
getFCP((metric) => track('Web Vital', { name: 'FCP', value: metric.value }))
getLCP((metric) => track('Web Vital', { name: 'LCP', value: metric.value }))
getTTFB((metric) => track('Web Vital', { name: 'TTFB', value: metric.value }))
})
}
}, [])
return null
}
Best Practices Checklist
- Implement privacy-compliant tracking
- Get user consent for cookies
- Track key conversion events
- Set up funnel analysis
- Implement A/B testing
- Track user properties
- Monitor Core Web Vitals
- Set up goal tracking
- Implement session recording (with privacy)
- Track errors and exceptions
- Use cohort analysis
- Respect Do Not Track
- Document tracked events
- Regular analytics review
When to Use This Skill
Invoke this skill when:
- Setting up analytics tracking
- Implementing conversion tracking
- Creating A/B tests
- Tracking user behavior
- Setting up funnels
- Implementing GDPR compliance
- Debugging analytics issues
- Optimizing conversions
- Creating analytics dashboards
- Tracking custom events