| 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 trackingapp/tools/qr-code/page.tsx- File upload trackingapp/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:
- Page view - Track when tool loads
- Primary action - Track main feature usage
- Success/failure - Track operation outcomes
- Exports - Track when users export/download
- Settings - Track feature toggle/configuration changes
- 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:
- Is this PII? → Don't track it
- Can this be anonymized? → Anonymize it
- Is this useful for metrics? → Track it safely