Files
klz-cables.com/components/analytics/README.md
Marc Mintel 72711c74ba
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Failing after 13m20s
umami
2026-01-24 22:03:06 +01:00

444 lines
12 KiB
Markdown

# Umami Analytics Integration
This project uses [Umami Analytics](https://umami.is/) 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:
```bash
# 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:
```yaml
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:
```tsx
// 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:
```tsx
'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
```tsx
'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:
```tsx
'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
```tsx
'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
```tsx
'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
```tsx
'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
```tsx
'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:
```tsx
trackEvent('custom_event_name', {
custom_property: 'value',
another_property: 123,
});
```
## Best Practices
### 1. Use Centralized Event Definitions
Always use the `AnalyticsEvents` constant for consistency:
```tsx
// ✅ 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:
```tsx
// ✅ 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:
```bash
# 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:**
```bash
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:
```bash
# .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
- [Umami Documentation](https://umami.is/docs)
- [Next.js Script Component](https://nextjs.org/docs/app/api-reference/components/script)
- [Analytics Best Practices](https://umami.is/docs/best-practices)
## 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