~/logs/2
root@zourdy:~$ cat log_002.md
> status: [ACTIVE] [FEATURED: TRUE]
> category: ANALYTICS
> views: 1,923
Analytics
FEATURED

> 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.

Sarah Johnson|Privacy Engineer
1/10/2024
12 min read
1,923 views
#Umami#Analytics#Privacy#GDPR#Next.js

> 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

root@zourdy:~$ ls -la related_logs/
> found 1 related entries
LOG: ACTIVE
VIEWS: 1,923
[LAST_MODIFIED] 1/18/2024, 12:00:00 AM