Dev Dissection — Week 10: Tracking & Events

Welcome back to Dev Dissection! In previous weeks, we’ve built a complete TODO app and deployed on Cloud run. This week, we’re adding the intelligence layer that helps you understand how users actually interact with your app.

By the end of this lesson, you’ll know what to track, how to track it, and how to turn data into actionable insights.

Prerequisites

  • Your TODO app
  • Basic understanding of user experience concepts
  • A Amplitude or Hotjar account (we’ll create these together)

Why Tracking Matters: The Data-Driven Development Mindset

Imagine launching your TODO app and asking yourself:

  • Are users actually completing tasks?
  • Where do they get stuck during onboarding?
  • Which features drive engagement vs. which are ignored?
  • What causes users to abandon the app?

Without tracking, you’re flying blind. With proper analytics, every product decision becomes evidence-based.

Real-World Impact

  • Dropbox increased signups 60% by tracking where users dropped off
  • Slack discovered users who invite teammates are 90% more likely to stick around
  • Spotify uses behavioral data to personalize the entire experience

The Complete Tracking Strategy

1. Product Analytics (Amplitude)

What it tracks: User actions, feature usage, conversion funnels

Focus: Understanding user behavior and product performance

2. User Experience Analytics (Hotjar)

What it tracks: Mouse movements, scrolling, clicks, session recordings

Focus: Understanding how users interact with your UI

3. Custom Business Metrics

What it tracks: Domain-specific events that matter to your business

Focus: Measuring success against your specific goals

What to Track: The Complete Framework

Core User Journey Events

// Authentication Flow
- User Registration Started
- User Registration Completed
- User Login Attempted
- User Login Successful
- User Login Failed
- User Logout

// Core Feature Usage
- Todo Created
- Todo Completed
- Todo Deleted
- Todo List Viewed
- Bulk Actions Used

// Engagement Patterns
- Session Started
- Session Duration
- Page Views
- Feature Discovery

Advanced Behavioral Events

// User Onboarding
- Onboarding Step Completed
- Tutorial Skipped
- Help Documentation Accessed
- Feature Tour Started/Completed

// User Retention Indicators
- Daily Active User
- Weekly Active User
- Return Visit (after 1 day, 7 days, 30 days)
- Consecutive Daily Usage

// Product Health Metrics
- Error Encountered
- API Request Failed
- Page Load Time
- Performance Issues

Business-Specific Metrics

// TODO App Specific
- Tasks Per Session
- Completion Rate
- Time to First Todo
- User Productivity Score
- Feature Adoption Rate

Setting Up Amplitude: Product Analytics

Step 1: Create Amplitude Account

  1. Go to amplitude.com
  2. Sign up for a free account (10M events/month)
  3. Create a new project: “TODO App Analytics”
  4. Note your API key from Settings

Step 2: Install Amplitude SDK

npm install @amplitude/analytics-browser
npm install -D @types/amplitude-js

Step 3: Create Analytics Service

Create lib/analytics.ts:

import { init, track, setUserId, reset } from '@amplitude/analytics-browser';

class AnalyticsService {
  private isInitialized = false;

  init() {
    if (typeof window === 'undefined' || this.isInitialized) return;

    init(process.env.NEXT_PUBLIC_ANALYTICS!, {
      defaultTracking: {
        sessions: true,
        pageViews: true,
        formInteractions: true,
        fileDownloads: true,
      },
      autocapture: false,
    });

    this.isInitialized = true;
  }

  // User Management
  identifyUser(userId: string, properties?: Record<string, unknown>) {
    setUserId(userId);
    if (properties) {
      this.setUserProperties(properties);
    }
  }

  resetUser() {
    reset();
  }

  setUserProperties(properties: Record<string, unknown>) {
    track('$identify', properties);
  }

  // Core Tracking Methods
  trackEvent(eventName: string, properties?: Record<string, unknown>) {
    if (!this.isInitialized) return;

    const enrichedProperties = {
      ...properties,
      timestamp: new Date().toISOString(),
      url: window.location.href,
      userAgent: navigator.userAgent,
    };

    track(eventName, enrichedProperties);
    console.log(`Tracked: ${eventName}`, enrichedProperties);
  }

  // Authentication Events
  trackRegistrationStarted() {
    this.trackEvent('Registration Started');
  }

  trackRegistrationCompleted(method: string) {
    this.trackEvent('Registration Completed', { method });
  }

  trackLoginAttempted(method: string) {
    this.trackEvent('Login Attempted', { method });
  }

  trackLoginSuccessful(method: string, userId: string) {
    this.trackEvent('Login Successful', { method });
    this.identifyUser(userId);
  }

  trackLoginFailed(method: string, error: string) {
    this.trackEvent('Login Failed', { method, error });
  }

  trackLogout() {
    this.trackEvent('User Logout');
    this.resetUser();
  }

  // --------------- TODO events ----------------- //
  trackTodoCreated(todoData: { hasText: boolean; characterCount: number }) {
    this.trackEvent('Todo Created', {
      ...todoData,
      source: 'main_input',
    });
  }

  trackTodoCompleted(todoData: { isCompleted: boolean; todoLength: number }) {
    this.trackEvent('Todo Completed', todoData);
  }

  trackTodoDeleted(todoData: { wasCompleted: boolean; age: number }) {
    this.trackEvent('Todo Deleted', todoData);
  }

  trackTodoListViewed(metrics: { totalTodos: number; completedTodos: number }) {
    this.trackEvent('Todo List Viewed', {
      ...metrics,
      completionRate:
        metrics.totalTodos > 0
          ? (metrics.completedTodos / metrics.totalTodos) * 100
          : 0,
    });
  }

  // User Engagement
  trackFeatureDiscovered(
    feature: string,
    discoveryMethod: 'click' | 'hover' | 'scroll'
  ) {
    this.trackEvent('Feature Discovered', { feature, discoveryMethod });
  }

  trackSessionStart() {
    this.trackEvent('Session Started', {
      referrer: document.referrer,
      timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
    });
  }

  // Performance Tracking
  trackPageLoadTime(page: string, loadTime: number) {
    this.trackEvent('Page Load Performance', { page, loadTime });
  }

  trackAPIError(endpoint: string, error: string, statusCode?: number) {
    this.trackEvent('API Error', { endpoint, error, statusCode });
  }

  // Business Metrics
  trackUserProductivity(metrics: {
    todosCreated: number;
    todosCompleted: number;
    sessionDuration: number;
  }) {
    this.trackEvent('User Productivity', {
      ...metrics,
      productivityScore:
        metrics.todosCompleted / Math.max(metrics.todosCreated, 1),
    });
  }
}

export const analytics = new AnalyticsService();

Step 4: Integrate with Auth Context

Update lib/auth-context.tsx:

'use client';

import { createContext, useContext, useEffect, useState } from 'react';
import { TodoAPI } from './api';
import { analytics } from './analytics';

// ... existing interfaces ...

export function AuthProvider({ children }: { children: React.ReactNode }) {
  // ... existing state ...

  useEffect(() => {
    analytics.init();
    analytics.trackSessionStart();

    // Check for stored token on mount
    const storedToken = localStorage.getItem('token');
    const storedUser = localStorage.getItem('user');

    if (storedToken && storedUser) {
      const user = JSON.parse(storedUser);
      setToken(storedToken);
      setUser(user);
      analytics.identifyUser(user.id, {
        email: user.email,
        name: user.name,
        lastLogin: new Date().toISOString(),
      });
    }
    setIsLoading(false);
  }, []);

  const login = async (email: string, password: string) => {
    analytics.trackLoginAttempted('email');
    
    try {
      const response = await TodoAPI.login(email, password);
      setToken(response.token);
      setUser(response.user);
      localStorage.setItem('token', response.token);
      localStorage.setItem('user', JSON.stringify(response.user));
      
      analytics.trackLoginSuccessful('email', response.user.id);
    } catch (error) {
      analytics.trackLoginFailed('email', error.message);
      throw error;
    }
  };

  const register = async (email: string, password: string, name: string) => {
    analytics.trackRegistrationStarted();
    
    try {
      const response = await TodoAPI.register(email, password, name);
      setToken(response.token);
      setUser(response.user);
      localStorage.setItem('token', response.token);
      localStorage.setItem('user', JSON.stringify(response.user));
      
      analytics.trackRegistrationCompleted('email');
      analytics.identifyUser(response.user.id, {
        email: response.user.email,
        name: response.user.name,
        registrationDate: new Date().toISOString(),
      });
    } catch (error) {
      throw error;
    }
  };

  const logout = () => {
    analytics.trackLogout();
    setToken(null);
    setUser(null);
    localStorage.removeItem('token');
    localStorage.removeItem('user');
  };

  // ... rest of component
}

Step 5: Track TODO Interactions

Update your components/todo-list.tsx:

'use client';

import { useEffect, useState } from 'react';
import { TodoAPI } from '@/lib/api';
import { analytics } from '@/lib/analytics';
import type { Todo } from '@/lib/types';

export function TodoList() {
  const [todos, setTodos] = useState<Todo[]>([]);
  const [newTask, setNewTask] = useState('');
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    loadTodos();
  }, []);

  const loadTodos = async () => {
    try {
      const data = await TodoAPI.getTodos();
      setTodos(data);
      
      // Track list view with metrics
      analytics.trackTodoListViewed({
        totalTodos: data.length,
        completedTodos: data.filter(t => t.completed).length,
      });
    } catch (error) {
      analytics.trackAPIError('/todos', error.message);
    } finally {
      setIsLoading(false);
    }
  };

  const handleCreateTodo = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!newTask.trim()) return;

    try {
      const todo = await TodoAPI.createTodo({ task: newTask });
      setTodos([...todos, todo]);
      
      // Track todo creation with context
      analytics.trackTodoCreated({
        hasText: newTask.trim().length > 0,
        characterCount: newTask.length,
      });
      
      setNewTask('');
    } catch (error) {
      analytics.trackAPIError('/todos', error.message);
    }
  };

  const handleToggleTodo = async (todo: Todo) => {
    try {
      const updated = await TodoAPI.toggleTodo(todo._id, {
        completed: !todo.completed,
      });
      
      setTodos(todos.map(t => t._id === todo._id ? updated : t));
      
      if (updated.completed) {
        // Calculate time to completion if we had creation time
        analytics.trackTodoCompleted({
          todoLength: todo.task.length,
          timeToComplete: todo.createdAt ? 
            Date.now() - new Date(todo.createdAt).getTime() : undefined,
        });
      }
    } catch (error) {
      analytics.trackAPIError(`/todos/${todo._id}`, error.message);
    }
  };

  const handleDeleteTodo = async (todo: Todo) => {
    try {
      await TodoAPI.deleteTodo(todo._id);
      setTodos(todos.filter(t => t._id !== todo._id));
      
      analytics.trackTodoDeleted({
        wasCompleted: todo.completed,
        age: todo.createdAt ? 
          Date.now() - new Date(todo.createdAt).getTime() : 0,
      });
    } catch (error) {
      analytics.trackAPIError(`/todos/${todo._id}`, error.message);
    }
  };

  // ... rest of component with tracking calls
}

Setting Up Hotjar: User Experience Analytics

Step 1: Create Hotjar Account

  1. Go to hotjar.com
  2. Sign up for free (up to 35 sessions/day)
  3. Add your site: http://localhost:3000 (for dev)
  4. Get your tracking code

Step 2: Add Hotjar to Next.js

Update auth-context.tsx:

'use client';

import { createContext, useContext, useEffect, useState } from 'react';
import { TodoAPI } from './api';
import { analytics } from './analytics';
import { hotjar } from './hotjar';

interface User {
  id: string;
  email: string;
  name: string;
}

interface AuthContextType {
  user: User | null;
  token: string | null;
  login: (email: string, password: string) => Promise<void>;
  register: (email: string, password: string, name: string) => Promise<void>;
  logout: () => void;
  isLoading: boolean;
}

export const AuthContext = createContext<AuthContextType | null>(null);

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const [token, setToken] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    analytics.init();
    analytics.trackSessionStart();
    
    // add hotjar init
    hotjar.init();

    // Check for stored token on mount
    const storedToken = localStorage.getItem('token');
    const storedUser = localStorage.getItem('user');

    if (storedToken && storedUser) {
      const user = JSON.parse(storedUser);
      setToken(storedToken);
      setUser(user);
      analytics.identifyUser(user.id, {
        email: user.email,
        name: user.name,
        lastLogin: new Date().toISOString(),
      });
    }
    setIsLoading(false);
  }, []);

  const login = async (email: string, password: string) => {
    analytics.trackLoginAttempted('email');

    try {
      const response = await TodoAPI.login(email, password);
      setToken(response.token);
      setUser(response.user);
      localStorage.setItem('token', response.token);
      localStorage.setItem('user', JSON.stringify(response.user));

      analytics.trackLoginSuccessful('email', response.user.id);
    } catch (error) {
      const e = error as Error;
      analytics.trackLoginFailed('email', e.message);
      throw error;
    }
  };

  const register = async (email: string, password: string, name: string) => {
    analytics.trackRegistrationStarted();

    try {
      const response = await TodoAPI.register(email, password, name);
      setToken(response.token);
      setUser(response.user);
      localStorage.setItem('token', response.token);
      localStorage.setItem('user', JSON.stringify(response.user));

      analytics.trackRegistrationCompleted('email');
      analytics.identifyUser(response.user.id, {
        email: response.user.email,
        name: response.user.name,
        registrationDate: new Date().toISOString(),
      });
    } catch (error) {
      throw error;
    }
  };

  const logout = () => {
    analytics.trackLogout();
    setToken(null);
    setUser(null);
    localStorage.removeItem('token');
    localStorage.removeItem('user');
  };

  return (
    <AuthContext.Provider
      value={{ user, token, login, register, logout, isLoading }}
    >
      {children}
    </AuthContext.Provider>
  );
}

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  return context;
};

Step 3: Create Hotjar Integration

Create lib/hotjar.ts:

import Hotjar from '@hotjar/browser';

class HotjarService {
  private isInitialized = false;

  init() {
    if (typeof window === 'undefined' || this.isInitialized) return;

    const siteId = ****;
    const hotjarVersion = *;

    Hotjar.init(siteId, hotjarVersion);

    this.isInitialized = true;
  }
}

export const hotjar = new HotjarService();

Advanced Tracking Patterns

1. Funnel Analysis Setup

Create lib/funnels.ts:

import { analytics } from './analytics';

export class FunnelTracker {
  // User Onboarding Funnel
  static trackOnboardingFunnel(step: string, metadata?: Record<string, any>) {
    const funnelSteps = [
      'landing_page_viewed',
      'registration_started',
      'registration_completed', 
      'first_todo_created',
      'first_todo_completed',
      'second_session_started',
    ];

    analytics.trackEvent('Onboarding Funnel Step', {
      step,
      stepIndex: funnelSteps.indexOf(step),
      totalSteps: funnelSteps.length,
      ...metadata,
    });
  }

  // Feature Adoption Funnel
  static trackFeatureAdoption(feature: string, action: string) {
    analytics.trackEvent('Feature Adoption', {
      feature,
      action, // 'discovered', 'attempted', 'completed'
    });
  }

  // Retention Funnel
  static trackRetentionMilestone(milestone: string, daysSinceRegistration: number) {
    analytics.trackEvent('Retention Milestone', {
      milestone, // 'day_1_return', 'day_7_return', 'day_30_return'
      daysSinceRegistration,
    });
  }
}

Tracking Implementation Guide

Update Your TODO Components

Here’s how to add tracking to your existing components:

In your Auth Form:

const handleSubmit = async (e: React.FormEvent) => {
  e.preventDefault();
  setIsLoading(true);
  setError('');

  try {
    if (mode === 'register') {
      analytics.trackRegistrationStarted();
      await register(email, password, name);
      FunnelTracker.trackOnboardingFunnel('registration_completed');
    } else {
      await login(email, password);
    }
  } catch (err) {
    setError(err instanceof Error ? err.message : 'An error occurred');
    ErrorTracker.trackUserError('auth_form', err.message);
  } finally {
    setIsLoading(false);
  }
};

In your Todo Components:

const createTodo = async () => {
  
  try {
    const todo = await TodoAPI.createTodo({ task: newTask });
    
    analytics.trackTodoCreated({
      hasText: newTask.trim().length > 0,
      characterCount: newTask.length,
    });

    // Track onboarding milestone
    if (todos.length === 0) {
      FunnelTracker.trackOnboardingFunnel('first_todo_created');
    }
    
    setTodos([...todos, todo]);
    setNewTask('');
  } catch (error) {
    ErrorTracker.trackAPIError('/todos', 'POST', 500, error.message);
  }
};

Advanced Analytics Dashboards

Creating Custom Amplitude Charts

1. User Engagement Dashboard

  • Daily Active Users: Track Session Started events
  • User Retention: Cohort analysis on registration date

2. Product Performance Dashboard

  • Todo Creation Rate: Todo Created events over time
  • Completion Funnel: Todo CreatedTodo Completed conversion
  • User Productivity: Average todos completed per user per day

3. Technical Health Dashboard

  • Error Rate: API Error and JavaScript Error frequency

Creating Hotjar Insights

1. Heatmaps Setup

  • Click Heatmaps: See what users click most
  • Scroll Heatmaps: Understand content consumption
  • Move Heatmaps: Track mouse movement patterns

3. Conversion Funnels

  • Registration Funnel: Landing → Register → First Todo
  • Engagement Funnel: Login → View Todos → Create Todo → Complete Todo

Privacy & GDPR Compliance

Step 1: Add Consent Management

Create components/consent-banner.tsx:

'use client';

import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';

export function ConsentBanner() {
  const [showBanner, setShowBanner] = useState(false);

  useEffect(() => {
    const consent = localStorage.getItem('analytics_consent');
    if (!consent) {
      setShowBanner(true);
    }
  }, []);

  const handleAccept = () => {
    localStorage.setItem('analytics_consent', 'accepted');
    setShowBanner(false);
    
    // Initialize analytics after consent
    analytics.init();
  };

  const handleDecline = () => {
    localStorage.setItem('analytics_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 z-50">
      <div className="container mx-auto flex items-center justify-between">
        <p className="text-sm">
          We use analytics to improve your experience. No personal data is sold.
        </p>
        <div className="flex gap-2">
          <Button variant="outline" size="sm" onClick={handleDecline}>
            Decline
          </Button>
          <Button size="sm" onClick={handleAccept}>
            Accept
          </Button>
        </div>
      </div>
    </div>
  );
}

Step 2: Conditional Analytics

Update analytics service to respect consent:

class AnalyticsService {
  private hasConsent(): boolean {
    return localStorage.getItem('analytics_consent') === 'accepted';
  }

  trackEvent(eventName: string, properties?: Record<string, any>) {
    if (!this.hasConsent() || !this.isInitialized) return;
    
    // ... rest of tracking logic
  }
}

Key Metrics to Monitor

Product Metrics

  • Activation Rate: % of users who create their first todo within 24 hours
  • Engagement Score: Average todos completed per active user
  • Retention Rates: Day 1, Day 7, Day 30 user return rates
  • Feature Adoption: % of users who discover and use each feature

Technical Metrics

  • Error Rate: % of requests that result in errors
  • Crash Rate: % of sessions with JavaScript errors
  • API Health: Success rates for each endpoint

Business Metrics

  • User Productivity: Average todos completed per user per session
  • Session Quality: Time spent actively using the app
  • User Journey Completion: % of users who complete registration → first todo → first completion
  • Churn Indicators: Patterns that predict user abandonment

Actionable Insights: What to Do with Your Data

Week 1 Analysis

Look for:

  • Drop-off Points: Where users abandon the registration flow
  • Popular Features: What gets used most vs. least
  • Error Patterns: Common failure points

Action Items:

  • Simplify high-drop-off steps
  • Improve error messaging for common failures
  • Enhance popular features, consider removing unused ones

Week 2 Analysis

Look for:

  • User Behavior Patterns: Power users vs. casual users
  • Performance Issues: Slow operations affecting engagement
  • UX Friction: Areas where users struggle (via Hotjar)

Action Items:

  • Create user segments based on behavior
  • Optimize slow-performing features
  • Fix UX friction points discovered in recordings

Month 1 Analysis

Look for:

  • Cohort Retention: Which user groups stick around
  • Feature Correlation: Which features predict long-term usage
  • Seasonal Patterns: Usage trends over time

Action Items:

  • Focus retention efforts on high-value user segments
  • Double down on features that drive retention
  • Plan feature releases around usage patterns

Testing Your Analytics Implementation

Step 1: Manual Testing Checklist

// Test in browser console:

// 1. Check Amplitude events
amplitude.getInstance().logEvent('Test Event');

// 2. Check Hotjar
window.hj('event', 'test_event');

// 3. Verify user identification
analytics.identifyUser('test-user-123', { name: 'Test User' });

// 4. Test error tracking
throw new Error('Test error for tracking');

Step 2: Create Analytics Debug Mode

Add to your analytics service:

class AnalyticsService {
  private debugMode = process.env.NODE_ENV === 'development';

  trackEvent(eventName: string, properties?: Record<string, any>) {
    // ... existing logic ...

    if (this.debugMode) {
      console.group(`Analytics Event: ${eventName}`);
      console.table(enrichedProperties);
      console.groupEnd();
    }
  }
}

Common Tracking Mistakes to Avoid

1. Over-Tracking

Problem: Tracking every tiny interaction

Solution: Focus on events that tie to business outcomes

2. Under-Contextualizing

Problem: Events without enough context to be actionable

Solution: Always include relevant metadata

3. Inconsistent Naming

Problem: Similar events with different names

Solution: Create an event taxonomy and stick to it

4. Missing Error Context

Problem: Knowing errors happen but not why

Solution: Include user state and context with error events

5. Ignoring Privacy

Problem: Tracking without user consent

Solution: Always implement proper consent management

Production Deployment Checklist

Before Launch:

  • [ ] Replace development API keys with production keys
  • [ ] Update Hotjar site URL to production domain
  • [ ] Set up Amplitude production project
  • [ ] Implement proper consent management
  • [ ] Test all tracking in staging environment
  • [ ] Set up alerting for critical errors
  • [ ] Create initial dashboards for key metrics

Week 1 Post-Launch:

  • [ ] Monitor error rates and fix critical issues
  • [ ] Analyze user onboarding flow
  • [ ] Identify and fix UX friction points
  • [ ] Set up automated reports for stakeholders

What You Learned

This week, you:

  • Understood the analytics landscape – Product analytics vs. business metrics
  • Implemented comprehensive tracking – From basic events to advanced funnels
  • Set up professional tools – Amplitude and Hotjar integration
  • Learned privacy compliance – GDPR-friendly consent management
  • Created actionable dashboards – Turn data into insights

Real-World Analytics Case Studies

Case Study 1: Optimizing User Onboarding

The Problem: 60% of users register but never create their first todo

The Investigation:

// Track detailed onboarding steps
FunnelTracker.trackOnboardingFunnel('registration_completed');
FunnelTracker.trackOnboardingFunnel('dashboard_viewed');
FunnelTracker.trackOnboardingFunnel('create_button_clicked');
FunnelTracker.trackOnboardingFunnel('first_todo_created');

Hotjar Insights:

  • Session recordings showed users confused by empty state
  • Heatmaps revealed create button wasn’t obvious
  • Users spent 30+ seconds looking for how to add todos

The Solution:

  • Added onboarding tour for new users
  • Improved empty state with clear call-to-action
  • Added sample todos for inspiration

Results: First todo creation rate increased from 40% to 78%

Case Study 2: Reducing User Churn

The Problem: Users stop using the app after 3-4 days

The Investigation:

// Track engagement patterns
analytics.trackEvent('User Productivity', {
  todosCreated: sessionTodos,
  todosCompleted: sessionCompleted,
  sessionDuration: sessionLength,
  consecutiveDays: userStreak,
});

Discovery: Users with 0 completed todos in first session had 80% churn rate

The Solution:

  • Added completion celebrations and motivational messages
  • Implemented streak tracking and achievements
  • Created daily todo suggestions

Results: Day 7 retention improved from 25% to 45%

Amplitude vs Hotjar vs Custom: Decision Matrix

NeedAmplitudeHotjarCustom Solution
User Journey AnalysisPerfectLimitedComplex to build
Conversion FunnelsBuilt-inNoRequires work
Session RecordingsBuilt-inBuilt-inVery difficult
HeatmapsBuilt-inBuilt-inComplex
Real-time EventsYesLimitedFull control
Data OwnershipLimitedLimitedComplete
Setup ComplexityEasyVery easyHigh

Troubleshooting Common Issues

Analytics Not Firing

// Debug checklist:
console.log('Amplitude initialized:', amplitude.getInstance());
console.log('Hotjar available:', typeof window.hj);
console.log('User consent:', localStorage.getItem('analytics_consent'));

// Test events manually
analytics.trackEvent('Debug Test', { source: 'manual_test' });

CORS Issues with Analytics

// Add to your Express server
app.use(cors({
  origin: ['http://localhost:3000', 'https://yourdomain.com'],
  credentials: true,
}));

Events Not Appearing in Dashboard

  • Check API keys – Wrong environment keys are common
  • Verify internet connection – Events queue locally if offline
  • Check browser console – Look for JavaScript errors
  • Validate event names – Special characters can cause issues

Key Takeaways

What Good Analytics Looks Like

  1. Event-driven development – Every feature has success metrics
  2. User-centric insights – Data tells user stories, not just numbers
  3. Actionable dashboards – Metrics drive concrete product decisions
  4. Privacy-first approach – User trust through transparent data practices

Common Analytics Anti-Patterns

  • Vanity metrics – Tracking pageviews instead of engagement
  • Event spam – Too many events without context
  • Analysis paralysis – Collecting data but never acting on it
  • Privacy negligence – Tracking without consent or transparency

The Analytics Mindset

  • Hypothesis-driven – Form theories, then test with data
  • User-empathetic – Understand the human behind the metrics
  • Iterative – Continuously refine based on insights
  • Outcome-focused – Measure what matters for your goals

Your TODO app now has startup-level analytics capabilities. You can understand user behavior, optimize for engagement, and make data-driven product decisions.

Coming Up: Monitoring & Scaling

Your app has analytics, but what happens when it breaks or gets overwhelmed by traffic? Next week, we’ll explore production monitoring and scaling strategies:

  • Sentry integration – Real-time error tracking, performance monitoring, and alert systems
  • Resource optimization – Database query tuning, memory management, and bottleneck identification
  • Scaling strategies – Load balancing, horizontal scaling, and infrastructure preparation

By the end, your TODO app will be production-ready and capable of handling real-world traffic with confidence.