Analytics Tracking
by ferryhinardi
Guide for implementing analytics tracking in SuperTool. Use this when adding analytics events, tracking user actions, or ensuring privacy compliance.
Skill Details
Repository Files
1 file in this skill directory
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
Related Skills
Dbt Transformation Patterns
Master dbt (data build tool) for analytics engineering with model organization, testing, documentation, and incremental strategies. Use when building data transformations, creating data models, or implementing analytics engineering best practices.
Anndata
This skill should be used when working with annotated data matrices in Python, particularly for single-cell genomics analysis, managing experimental measurements with metadata, or handling large-scale biological datasets. Use when tasks involve AnnData objects, h5ad files, single-cell RNA-seq data, or integration with scanpy/scverse tools.
Xlsx
Spreadsheet toolkit (.xlsx/.csv). Create/edit with formulas/formatting, analyze data, visualization, recalculate formulas, for spreadsheet processing and analysis.
Tensorboard
Visualize training metrics, debug models with histograms, compare experiments, visualize model graphs, and profile performance with TensorBoard - Google's ML visualization toolkit
Deeptools
NGS analysis toolkit. BAM to bigWig conversion, QC (correlation, PCA, fingerprints), heatmaps/profiles (TSS, peaks), for ChIP-seq, RNA-seq, ATAC-seq visualization.
Scvi Tools
This skill should be used when working with single-cell omics data analysis using scvi-tools, including scRNA-seq, scATAC-seq, CITE-seq, spatial transcriptomics, and other single-cell modalities. Use this skill for probabilistic modeling, batch correction, dimensionality reduction, differential expression, cell type annotation, multimodal integration, and spatial analysis tasks.
Statsmodels
Statistical modeling toolkit. OLS, GLM, logistic, ARIMA, time series, hypothesis tests, diagnostics, AIC/BIC, for rigorous statistical inference and econometric analysis.
Scikit Survival
Comprehensive toolkit for survival analysis and time-to-event modeling in Python using scikit-survival. Use this skill when working with censored survival data, performing time-to-event analysis, fitting Cox models, Random Survival Forests, Gradient Boosting models, or Survival SVMs, evaluating survival predictions with concordance index or Brier score, handling competing risks, or implementing any survival analysis workflow with the scikit-survival library.
Neurokit2
Comprehensive biosignal processing toolkit for analyzing physiological data including ECG, EEG, EDA, RSP, PPG, EMG, and EOG signals. Use this skill when processing cardiovascular signals, brain activity, electrodermal responses, respiratory patterns, muscle activity, or eye movements. Applicable for heart rate variability analysis, event-related potentials, complexity measures, autonomic nervous system assessment, psychophysiology research, and multi-modal physiological signal integration.
Statistical Analysis
Statistical analysis toolkit. Hypothesis tests (t-test, ANOVA, chi-square), regression, correlation, Bayesian stats, power analysis, assumption checks, APA reporting, for academic research.
