> status: [ACTIVE] [FEATURED: TRUE]
> category: ANALYTICS
> views: 1,923
> Implementing Umami Analytics: Privacy-First Web Analytics_
[ABSTRACT] Learn how to implement Umami analytics for privacy-focused website tracking without compromising user data or GDPR compliance.
> Implementing Umami Analytics: Privacy-First Web Analytics
In an era where privacy concerns are paramount, Umami offers a lightweight, privacy-focused alternative to Google Analytics. Let's explore how to implement and customize Umami for your web applications.
> Why Choose Umami?
Umami provides several advantages over traditional analytics platforms:
- Privacy-first approach: No cookies, no tracking across sites
- GDPR compliant: Respects user privacy by design
- Lightweight: Minimal impact on page load times
- Self-hosted: Complete control over your data
- Open source: Transparent and customizable
> Installation and Setup
> Self-Hosted Installation
First, let's set up Umami using Docker:
bash.sh# Clone the repository git clone https://github.com/umami-software/umami.git cd umami # Create environment file cp .env.example .env # Configure your database connection echo "DATABASE_URL=postgresql://username:password@localhost:5432/umami" >> .env echo "HASH_SALT=your-random-salt-here" >> .env # Start with Docker Compose docker-compose up -d
> Database Configuration
Set up your PostgreSQL database:
sql.sh-- Create database CREATE DATABASE umami; -- Create user CREATE USER umami_user WITH PASSWORD 'secure_password'; -- Grant privileges GRANT ALL PRIVILEGES ON DATABASE umami TO umami_user;
> Integration with Next.js
> Installing the Tracking Script
Add Umami to your Next.js application:
tsx.sh// app/layout.tsx import Script from 'next/script' export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( <html lang="en"> <head> <Script src="https://your-umami-domain.com/script.js" data-website-id="your-website-id" strategy="afterInteractive" /> </head> <body>{children}</body> </html> ) }
> Custom Event Tracking
Track custom events programmatically:
tsx.sh// lib/analytics.ts declare global { interface Window { umami: { track: (event: string, data?: Record<string, any>) => void } } } export const trackEvent = (event: string, data?: Record<string, any>) => { if (typeof window !== 'undefined' && window.umami) { window.umami.track(event, data) } } // Usage in components export function ContactForm() { const handleSubmit = async (formData: FormData) => { try { await submitForm(formData) // Track successful form submission trackEvent('contact-form-submit', { source: 'contact-page', method: 'email' }) setSuccess(true) } catch (error) { trackEvent('contact-form-error', { error: error.message }) } } return ( <form action={handleSubmit}> {/* Form fields */} </form> ) }
> Advanced Tracking Hook
Create a custom hook for analytics:
tsx.sh// hooks/useAnalytics.ts import { useEffect, useCallback } from 'react' import { usePathname } from 'next/navigation' export function useAnalytics() { const pathname = usePathname() // Track page views useEffect(() => { if (typeof window !== 'undefined' && window.umami) { window.umami.track('pageview') } }, [pathname]) // Track custom events const track = useCallback((event: string, data?: Record<string, any>) => { if (typeof window !== 'undefined' && window.umami) { window.umami.track(event, data) } }, []) // Track user interactions const trackClick = useCallback((element: string, data?: Record<string, any>) => { track('click', { element, ...data }) }, [track]) const trackDownload = useCallback((filename: string, type: string) => { track('download', { filename, type }) }, [track]) const trackSearch = useCallback((query: string, results: number) => { track('search', { query, results }) }, [track]) return { track, trackClick, trackDownload, trackSearch } }
> Custom Dashboard Integration
> API Integration
Access Umami data programmatically:
tsx.sh// lib/umami-api.ts interface UmamiStats { pageviews: { value: number; change: number } visitors: { value: number; change: number } bounces: { value: number; change: number } totaltime: { value: number; change: number } } export class UmamiAPI { private baseUrl: string private token: string constructor(baseUrl: string, token: string) { this.baseUrl = baseUrl this.token = token } async getWebsiteStats(websiteId: string, startAt: number, endAt: number): Promise<UmamiStats> { const response = await fetch( `${this.baseUrl}/api/websites/${websiteId}/stats?startAt=${startAt}&endAt=${endAt}`, { headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' } } ) if (!response.ok) { throw new Error('Failed to fetch website stats') } return response.json() } async getPageViews(websiteId: string, startAt: number, endAt: number) { const response = await fetch( `${this.baseUrl}/api/websites/${websiteId}/pageviews?startAt=${startAt}&endAt=${endAt}`, { headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' } } ) return response.json() } }
> Analytics Dashboard Component
tsx.sh// components/AnalyticsDashboard.tsx import { useState, useEffect } from 'react' import { UmamiAPI } from '@/lib/umami-api' interface AnalyticsDashboardProps { websiteId: string } export function AnalyticsDashboard({ websiteId }: AnalyticsDashboardProps) { const [stats, setStats] = useState(null) const [loading, setLoading] = useState(true) useEffect(() => { const fetchStats = async () => { try { const api = new UmamiAPI( process.env.NEXT_PUBLIC_UMAMI_URL!, process.env.UMAMI_API_TOKEN! ) const endAt = Date.now() const startAt = endAt - (30 * 24 * 60 * 60 * 1000) // 30 days ago const data = await api.getWebsiteStats(websiteId, startAt, endAt) setStats(data) } catch (error) { console.error('Failed to fetch analytics:', error) } finally { setLoading(false) } } fetchStats() }, [websiteId]) if (loading) { return <div className="animate-pulse">Loading analytics...</div> } return ( <div className="grid grid-cols-1 md:grid-cols-4 gap-6"> <StatCard title="Page Views" value={stats?.pageviews.value} change={stats?.pageviews.change} /> <StatCard title="Unique Visitors" value={stats?.visitors.value} change={stats?.visitors.change} /> <StatCard title="Bounce Rate" value={`${stats?.bounces.value}%`} change={stats?.bounces.change} /> <StatCard title="Avg. Session" value={`${Math.round(stats?.totaltime.value / 60)}m`} change={stats?.totaltime.change} /> </div> ) }
> Best Practices
> 1. Respect User Privacy
Always inform users about analytics collection:
tsx.sh// components/PrivacyNotice.tsx export function PrivacyNotice() { return ( <div className="bg-blue-50 border border-blue-200 rounded-lg p-4"> <p className="text-sm text-blue-800"> We use privacy-friendly analytics to improve our website. No personal data is collected or stored. </p> </div> ) }
> 2. Environment Configuration
bash.sh# .env.local NEXT_PUBLIC_UMAMI_URL=https://your-umami-domain.com NEXT_PUBLIC_UMAMI_WEBSITE_ID=your-website-id UMAMI_API_TOKEN=your-api-token
> Conclusion
Umami provides a powerful, privacy-focused analytics solution that respects user privacy while delivering valuable insights. By implementing proper tracking and respecting user privacy, you can build trust with your audience while gaining the data you need to improve your application.
RELATED.LOGS
> found 1 related entries