Files
klz-cables.com/components/analytics
Marc Mintel 5c71e9a064
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m36s
env
2026-01-27 00:30:12 +01:00
..
2026-01-24 22:03:06 +01:00
env
2026-01-27 00:30:12 +01:00
2026-01-24 22:03:06 +01:00
2026-01-24 22:03:06 +01:00
2026-01-24 22:03:06 +01:00
2026-01-24 22:03:06 +01:00
2026-01-24 22:03:06 +01:00

Umami Analytics Integration

This project uses Umami Analytics for privacy-focused website analytics. The implementation is modern, clean, and follows Next.js best practices.

Overview

The analytics system consists of:

  1. UmamiScript - Loads the Umami tracking script
  2. AnalyticsProvider - Tracks pageviews on route changes
  3. useAnalytics - Custom hook for tracking custom events
  4. analytics-events.ts - Centralized event definitions
  5. UmamiAnalyticsService - Service layer for analytics operations

Setup

Environment Variables

Add these to your .env file:

# Required: Your Umami website ID
NEXT_PUBLIC_UMAMI_WEBSITE_ID=59a7db94-0100-4c7e-98ef-99f45b17f9c3

# Optional: Custom Umami script URL (defaults to https://analytics.infra.mintel.me/script.js)
NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js

Docker Compose

The docker-compose.yml already includes the environment variables:

environment:
  - NEXT_PUBLIC_UMAMI_WEBSITE_ID=${NEXT_PUBLIC_UMAMI_WEBSITE_ID}
  - NEXT_PUBLIC_UMAMI_SCRIPT_URL=${NEXT_PUBLIC_UMAMI_SCRIPT_URL:-https://analytics.infra.mintel.me/script.js}

Usage

1. Automatic Pageview Tracking

The AnalyticsProvider component automatically tracks pageviews on client-side route changes. It's already included in your layout:

// app/[locale]/layout.tsx
<NextIntlClientProvider messages={messages} locale={locale}>
  <UmamiScript />
  <Header />
  <main>{children}</main>
  <Footer />
  <AnalyticsProvider />
</NextIntlClientProvider>

2. Tracking Custom Events

Use the useAnalytics hook to track custom events:

'use client';

import { useAnalytics } from '@/components/analytics/useAnalytics';
import { AnalyticsEvents } from '@/components/analytics/analytics-events';

function ProductCard({ product }) {
  const { trackEvent } = useAnalytics();

  const handleAddToCart = () => {
    trackEvent(AnalyticsEvents.PRODUCT_ADD_TO_CART, {
      product_id: product.id,
      product_name: product.name,
      product_category: product.category,
      price: product.price,
    });
  };

  return (
    <button onClick={handleAddToCart}>
      Add to Cart
    </button>
  );
}

3. Tracking Pageviews Manually

'use client';

import { useAnalytics } from '@/components/analytics/useAnalytics';

function CustomNavigation() {
  const { trackPageview } = useAnalytics();

  const navigateToCustomPage = () => {
    // Track a custom pageview
    trackPageview('/custom-path?param=value');
    
    // Then perform navigation
    window.location.href = '/custom-path?param=value';
  };

  return <button onClick={navigateToCustomPage}>Go to Custom Page</button>;
}

4. Using Predefined Events

The analytics-events.ts file provides a centralized list of events:

'use client';

import { useAnalytics } from '@/components/analytics/useAnalytics';
import { AnalyticsEvents, AnalyticsEventProperties } from '@/components/analytics/analytics-events';

function ContactForm() {
  const { trackEvent } = useAnalytics();

  const handleSubmit = (formData: FormData) => {
    // Track form submission
    trackEvent(AnalyticsEvents.CONTACT_FORM_SUBMIT, {
      form_id: 'contact-form',
      form_name: 'Contact Us',
      form_fields: {
        name: formData.get('name'),
        email: formData.get('email'),
        message: formData.get('message'),
      },
    });
  };

  return <form onSubmit={handleSubmit}>{/* form fields */}</form>;
}

5. E-commerce Tracking Example

'use client';

import { useAnalytics } from '@/components/analytics/useAnalytics';
import { AnalyticsEvents } from '@/components/analytics/analytics-events';

function ProductPage({ product }) {
  const { trackEvent } = useAnalytics();

  // Track product view on page load
  useEffect(() => {
    trackEvent(AnalyticsEvents.PRODUCT_VIEW, {
      product_id: product.id,
      product_name: product.name,
      product_category: product.category,
      price: product.price,
    });
  }, [product]);

  const handleAddToCart = () => {
    trackEvent(AnalyticsEvents.PRODUCT_ADD_TO_CART, {
      product_id: product.id,
      product_name: product.name,
      product_category: product.category,
      price: product.price,
      quantity: 1,
    });
  };

  const handlePurchase = () => {
    trackEvent(AnalyticsEvents.PRODUCT_PURCHASE, {
      product_id: product.id,
      product_name: product.name,
      product_category: product.category,
      price: product.price,
      transaction_id: 'TXN-12345',
      currency: 'EUR',
    });
  };

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <button onClick={handleAddToCart}>Add to Cart</button>
      <button onClick={handlePurchase}>Buy Now</button>
    </div>
  );
}

6. Search & Filter Tracking

'use client';

import { useAnalytics } from '@/components/analytics/useAnalytics';
import { AnalyticsEvents } from '@/components/analytics/analytics-events';

function ProductFilter() {
  const { trackEvent } = useAnalytics();

  const handleFilterChange = (filters: Record<string, unknown>) => {
    trackEvent(AnalyticsEvents.FILTER_APPLY, {
      filter_type: 'category',
      filter_value: filters.category,
      filter_count: Object.keys(filters).length,
    });
  };

  const handleSearch = (query: string) => {
    trackEvent(AnalyticsEvents.SEARCH, {
      search_query: query,
      search_results_count: 42, // You'd get this from your search results
    });
  };

  return (
    <div>
      <input onChange={(e) => handleSearch(e.target.value)} />
      <select onChange={(e) => handleFilterChange({ category: e.target.value })}>
        {/* filter options */}
      </select>
    </div>
  );
}

7. User Account Events

'use client';

import { useAnalytics } from '@/components/analytics/useAnalytics';
import { AnalyticsEvents } from '@/components/analytics/analytics-events';

function LoginForm() {
  const { trackEvent } = useAnalytics();

  const handleLogin = (email: string) => {
    trackEvent(AnalyticsEvents.USER_LOGIN, {
      user_email: email,
      login_method: 'email',
    });
  };

  const handleLogout = () => {
    trackEvent(AnalyticsEvents.USER_LOGOUT, {
      user_id: 'user-123',
    });
  };

  return (
    <div>
      <button onClick={() => handleLogin('user@example.com')}>Login</button>
      <button onClick={handleLogout}>Logout</button>
    </div>
  );
}

8. Error Tracking

'use client';

import { useAnalytics } from '@/components/analytics/useAnalytics';
import { AnalyticsEvents } from '@/components/analytics/analytics-events';

function ErrorBoundary({ children }) {
  const { trackEvent } = useAnalytics();

  const handleError = (error: Error, errorInfo: React.ErrorInfo) => {
    trackEvent(AnalyticsEvents.ERROR, {
      error_message: error.message,
      error_stack: error.stack,
      error_component: errorInfo.componentStack,
    });
  };

  return (
    <ErrorBoundary onError={handleError}>
      {children}
    </ErrorBoundary>
  );
}

Event Reference

Common Events

Event Name Description Example Properties
pageview Page view { url: '/products/123' }
button_click Button clicked { button_id: 'cta-primary', page: 'homepage' }
link_click Link clicked { link_url: '/products', link_text: 'View Products' }
form_submit Form submitted { form_id: 'contact-form', form_name: 'Contact Us' }
product_view Product page viewed { product_id: '123', product_name: 'Cable', price: 99.99 }
product_add_to_cart Product added to cart { product_id: '123', quantity: 1 }
product_purchase Product purchased { product_id: '123', transaction_id: 'TXN-123' }
search Search performed { search_query: 'cable', search_results_count: 42 }
filter_apply Filter applied { filter_type: 'category', filter_value: 'high-voltage' }
user_login User logged in { user_email: 'user@example.com' }
user_signup User signed up { user_email: 'user@example.com' }
error Error occurred { error_message: 'Something went wrong' }

Custom Events

You can create any custom event by passing a string name:

trackEvent('custom_event_name', {
  custom_property: 'value',
  another_property: 123,
});

Best Practices

1. Use Centralized Event Definitions

Always use the AnalyticsEvents constant for consistency:

// ✅ Good
import { AnalyticsEvents } from '@/components/analytics/analytics-events';
trackEvent(AnalyticsEvents.PRODUCT_ADD_TO_CART, { ... });

// ❌ Avoid
trackEvent('product_add_to_cart', { ... }); // Typo risk!

2. Include Relevant Context

Add context to your events to make them more useful:

// ✅ Good
trackEvent(AnalyticsEvents.BUTTON_CLICK, {
  button_id: 'cta-primary',
  page: 'homepage',
  section: 'hero',
  user_type: 'guest',
});

// ❌ Less useful
trackEvent(AnalyticsEvents.BUTTON_CLICK, {
  button_id: 'cta-primary',
});

3. Track Meaningful Events

Focus on business-critical events:

  • Product views, add to cart, purchases

  • Form submissions (contact, newsletter, quote requests)

  • Search queries and filter usage

  • User authentication events

  • Error occurrences

  • Every mouse move

  • Every scroll event (unless specifically needed)

  • Every hover state change

4. Respect Privacy

  • Don't track personally identifiable information (PII)
  • Don't track sensitive data (passwords, credit cards, etc.)
  • Use anonymized IDs where possible
  • Follow GDPR and other privacy regulations

5. Test in Development

The analytics system includes development mode logging:

# In development, you'll see console logs:
[Umami] Tracked event: product_add_to_cart { product_id: '123' }
[Umami] Tracked pageview: /products/123

Troubleshooting

Analytics Not Working

  1. Check environment variables:

    echo $NEXT_PUBLIC_UMAMI_WEBSITE_ID
    
  2. Verify the script is loading:

    • Open browser DevTools
    • Check Network tab for script.js request
    • Check Console for any errors
  3. Check Umami dashboard:

    • Log into your Umami instance
    • Verify the website ID matches
    • Check if data is being received

Development Mode

In development mode, you'll see console logs for all tracked events. This helps you verify that events are being tracked correctly without affecting your production analytics.

Disabling Analytics

To disable analytics (e.g., for local development), simply remove the NEXT_PUBLIC_UMAMI_WEBSITE_ID environment variable:

# .env.local (not committed to git)
# NEXT_PUBLIC_UMAMI_WEBSITE_ID=

Performance

The analytics implementation is optimized for performance:

  • Uses Next.js Script component with afterInteractive strategy
  • Script loads after page is interactive
  • No blocking of critical rendering path
  • Minimal JavaScript bundle size
  • Automatic cleanup on route changes

Security

  • Environment variables are not exposed to the client (except NEXT_PUBLIC_ prefixed ones)
  • Script URL can be customized for self-hosted Umami instances
  • Error handling prevents analytics from breaking your app
  • Type-safe event tracking with TypeScript

Additional Resources

Support

For issues or questions about the analytics implementation, check:

  1. This README for usage examples
  2. The component source code for implementation details
  3. The Umami documentation for platform-specific questions