analytics-tracking

Guide for implementing analytics tracking in SuperTool. Use this when adding analytics events, tracking user actions, or ensuring privacy compliance.

$ 安裝

git clone https://github.com/ferryhinardi/supertool /tmp/supertool && cp -r /tmp/supertool/.github/skills/analytics-tracking ~/.claude/skills/supertool

// tip: Run this command in your terminal to install the skill


name: analytics-tracking description: Guide for implementing analytics tracking in SuperTool. Use this when adding analytics events, tracking user actions, or ensuring privacy compliance. license: MIT

Analytics Tracking Guide

This skill ensures proper analytics implementation while maintaining user privacy.

Core Principle: Privacy-First Analytics

CRITICAL: NEVER track personally identifiable information (PII).

What NOT to Track

  • ❌ User names, emails, IP addresses
  • ❌ Actual file names or file contents
  • ❌ Full URLs with potential sensitive data
  • ❌ User input text/data
  • ❌ Authentication tokens or credentials
  • ❌ Specific error messages with user data

What TO Track

  • ✅ Tool usage counts
  • ✅ Feature interactions (anonymized)
  • ✅ File types (not names): "image/png", "text/csv"
  • ✅ File sizes in ranges: "<1MB", "1-10MB", ">10MB"
  • ✅ Success/failure rates (without details)
  • ✅ Performance metrics (timing)
  • ✅ Generic error types: "validation_error", "network_error"

Analytics Function

import { trackToolEvent } from '@/lib/analytics'

// Function signature
trackToolEvent(
  toolId: string,      // Tool identifier from tools.ts
  action: string,      // Action name (snake_case)
  metadata?: object    // Optional metadata (NO PII!)
)

Implementation Pattern

1. Import the Function

import { trackToolEvent } from '@/lib/analytics'

2. Track User Actions

'use client'

export default function ToolPage() {
  const handleProcess = async () => {
    try {
      // Process user action
      const result = await processData(input)
      
      // ✅ Track success (anonymized metadata)
      trackToolEvent('tool-id', 'process_success', {
        fileType: file.type,
        fileSizeRange: getFileSizeRange(file.size),
        processingTime: Date.now() - startTime
      })
      
    } catch (error) {
      // ✅ Track error (generic type only)
      trackToolEvent('tool-id', 'process_error', {
        errorType: error instanceof ValidationError ? 'validation' : 'unknown'
      })
    }
  }
  
  return (
    // Component JSX
  )
}

Common Event Patterns

Tool Page View

// Track when tool is loaded (in useEffect)
useEffect(() => {
  trackToolEvent('tool-id', 'page_view')
}, [])

File Upload

const handleFileUpload = (file: File) => {
  trackToolEvent('tool-id', 'file_uploaded', {
    fileType: file.type,
    fileSizeRange: getFileSizeRange(file.size),
    // ❌ DON'T: fileName: file.name
  })
}

// Helper to anonymize file size
function getFileSizeRange(bytes: number): string {
  if (bytes < 1024 * 1024) return '<1MB'
  if (bytes < 10 * 1024 * 1024) return '1-10MB'
  if (bytes < 50 * 1024 * 1024) return '10-50MB'
  return '>50MB'
}

Form Submission

const handleSubmit = async (formData: FormData) => {
  const startTime = Date.now()
  
  try {
    const result = await submitForm(formData)
    
    trackToolEvent('tool-id', 'form_submitted', {
      fieldCount: formData.entries().length,
      processingTime: Date.now() - startTime
      // ❌ DON'T: formValues: Object.fromEntries(formData)
    })
  } catch (error) {
    trackToolEvent('tool-id', 'form_submission_failed', {
      errorType: 'network_error'
    })
  }
}

Export/Download

const handleExport = (format: string) => {
  trackToolEvent('tool-id', 'export_initiated', {
    format: format,
    itemCount: data.length
    // ❌ DON'T: fileName: exportFileName
  })
}

Feature Toggle

const handleToggleFeature = (featureName: string, enabled: boolean) => {
  trackToolEvent('tool-id', 'feature_toggled', {
    feature: featureName,
    enabled: enabled
  })
}

API Call

const callAPI = async (endpoint: string) => {
  const startTime = Date.now()
  
  try {
    const response = await fetch(endpoint)
    
    trackToolEvent('tool-id', 'api_call_success', {
      endpoint: endpoint.replace(/\/[^/]+$/, '/:id'), // Anonymize IDs
      statusCode: response.status,
      responseTime: Date.now() - startTime
    })
  } catch (error) {
    trackToolEvent('tool-id', 'api_call_failed', {
      endpoint: endpoint.replace(/\/[^/]+$/, '/:id'),
      errorType: 'network_error'
    })
  }
}

Copy to Clipboard

const handleCopy = () => {
  navigator.clipboard.writeText(result)
  
  trackToolEvent('tool-id', 'copy_to_clipboard', {
    resultLength: result.length > 1000 ? 'large' : 'small'
    // ❌ DON'T: copiedText: result
  })
}

Settings Change

const handleSettingChange = (setting: string, value: unknown) => {
  trackToolEvent('tool-id', 'setting_changed', {
    setting: setting,
    valueType: typeof value
    // ❌ DON'T: value: value
  })
}

Tab/Section Switch

const handleTabChange = (tabName: string) => {
  trackToolEvent('tool-id', 'tab_changed', {
    tab: tabName
  })
}

Search Query

const handleSearch = (query: string) => {
  trackToolEvent('tool-id', 'search_performed', {
    queryLength: query.length,
    hasSpecialChars: /[^a-zA-Z0-9\s]/.test(query)
    // ❌ DON'T: query: query
  })
}

Event Naming Conventions

Use snake_case for action names:

// ✅ GOOD
'file_uploaded'
'export_initiated'
'form_submitted'
'process_success'
'api_call_failed'

// ❌ BAD
'fileUploaded'
'ExportInitiated'
'Form Submitted'
'processSuccess'

Metadata Best Practices

Structure Metadata Objects

// ✅ GOOD - Structured, anonymized data
trackToolEvent('json-beautifier', 'format_success', {
  inputSize: '<1KB',
  outputFormat: 'indented',
  indentSize: 2,
  processingTime: 123
})

// ❌ BAD - Unstructured or contains PII
trackToolEvent('json-beautifier', 'format_success', {
  data: jsonData,  // Don't send actual data
  user: userId     // Don't send user identifiers
})

Keep Metadata Concise

// ✅ GOOD - Essential info only
trackToolEvent('image-optimizer', 'optimization_complete', {
  originalSize: '5-10MB',
  optimizedSize: '1-5MB',
  compressionRatio: 0.7,
  format: 'jpeg'
})

// ❌ BAD - Too verbose or sensitive
trackToolEvent('image-optimizer', 'optimization_complete', {
  originalFileName: file.name,
  originalPath: file.path,
  optimizedFileName: output.name,
  fullMetadata: image.metadata
})

Performance Tracking

Track performance metrics to identify bottlenecks:

const startTime = performance.now()

try {
  const result = await expensiveOperation()
  
  const duration = performance.now() - startTime
  
  trackToolEvent('tool-id', 'operation_complete', {
    duration: Math.round(duration),
    success: true
  })
} catch (error) {
  const duration = performance.now() - startTime
  
  trackToolEvent('tool-id', 'operation_complete', {
    duration: Math.round(duration),
    success: false,
    errorType: 'processing_error'
  })
}

A/B Testing Support

Track which variant users see:

const variant = getABTestVariant('feature-x')

trackToolEvent('tool-id', 'feature_viewed', {
  variant: variant, // 'control' or 'treatment'
  feature: 'feature-x'
})

Error Categorization

Categorize errors generically:

const categorizeError = (error: Error): string => {
  if (error instanceof ValidationError) return 'validation'
  if (error instanceof NetworkError) return 'network'
  if (error instanceof TimeoutError) return 'timeout'
  return 'unknown'
}

trackToolEvent('tool-id', 'error_occurred', {
  errorCategory: categorizeError(error),
  // ❌ DON'T: errorMessage: error.message
})

Testing Analytics

Mock analytics in tests:

import { describe, expect, it, vi } from 'vitest'
import { trackToolEvent } from '@/lib/analytics'

// Mock the analytics module
vi.mock('@/lib/analytics', () => ({
  trackToolEvent: vi.fn(),
}))

describe('Component', () => {
  it('tracks user action', async () => {
    const { trackToolEvent } = await import('@/lib/analytics')
    
    // Trigger action
    await user.click(button)
    
    // Verify tracking
    expect(trackToolEvent).toHaveBeenCalledWith(
      'tool-id',
      'action_name',
      expect.objectContaining({
        // Expected metadata
      })
    )
  })
  
  it('does not track PII', async () => {
    const { trackToolEvent } = await import('@/lib/analytics')
    
    await user.type(input, 'sensitive data')
    await user.click(button)
    
    // Verify no sensitive data in any call
    const calls = vi.mocked(trackToolEvent).mock.calls
    calls.forEach(call => {
      const metadata = call[2]
      expect(JSON.stringify(metadata)).not.toContain('sensitive data')
    })
  })
})

Analytics Dashboard Metrics

Track metrics useful for dashboard:

// Daily Active Tools
trackToolEvent('tool-id', 'page_view')

// Feature Adoption
trackToolEvent('tool-id', 'feature_used', { feature: 'export-pdf' })

// Conversion Rate
trackToolEvent('tool-id', 'conversion', { from: 'view', to: 'action' })

// User Engagement
trackToolEvent('tool-id', 'session_duration', { duration: sessionTime })

// Error Rate
trackToolEvent('tool-id', 'error_occurred', { errorType: 'validation' })

Compliance Checklist

Before deploying analytics:

  • No PII tracked (names, emails, IPs, etc.)
  • File names anonymized or not tracked
  • URLs sanitized (IDs/tokens removed)
  • User input not tracked
  • Error messages anonymized
  • Metadata is concise and relevant
  • Event names follow snake_case convention
  • Tests verify no PII in tracking calls
  • Analytics calls wrapped in try/catch
  • Performance impact minimal (async tracking)

Reference Implementation

See existing tools for examples:

  • app/tools/split-bill/page.tsx - Comprehensive tracking
  • app/tools/qr-code/page.tsx - File upload tracking
  • app/tools/json-beautifier/page.tsx - Processing tracking

Common Mistakes to Avoid

❌ Tracking User Input

// DON'T
trackToolEvent('password-generator', 'generated', {
  password: generatedPassword  // Never track passwords!
})

❌ Tracking File Names

// DON'T
trackToolEvent('file-converter', 'converted', {
  fileName: file.name  // File names might contain PII
})

❌ Tracking Full URLs

// DON'T
trackToolEvent('url-shortener', 'shortened', {
  originalUrl: url  // URLs might contain tokens/sensitive data
})

❌ Detailed Error Messages

// DON'T
trackToolEvent('tool-id', 'error', {
  errorMessage: error.message  // Might contain sensitive info
})

✅ Correct Alternatives

// DO - Anonymized tracking
trackToolEvent('password-generator', 'generated', {
  length: password.length,
  hasSpecialChars: includesSpecialChars,
  strength: 'strong'
})

trackToolEvent('file-converter', 'converted', {
  fileType: file.type,
  fileSizeRange: getFileSizeRange(file.size)
})

trackToolEvent('url-shortener', 'shortened', {
  urlLength: url.length > 100 ? 'long' : 'short',
  protocol: new URL(url).protocol
})

trackToolEvent('tool-id', 'error', {
  errorType: categorizeError(error)
})

Implementation in New Tools

When creating a new tool, add analytics for:

  1. Page view - Track when tool loads
  2. Primary action - Track main feature usage
  3. Success/failure - Track operation outcomes
  4. Exports - Track when users export/download
  5. Settings - Track feature toggle/configuration changes
  6. Errors - Track error types (anonymized)

Summary

Golden Rule: If you wouldn't want it tracked about yourself, don't track it about users.

When in doubt, ask:

  1. Is this PII? → Don't track it
  2. Can this be anonymized? → Anonymize it
  3. Is this useful for metrics? → Track it safely