Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 1m14s
Build & Deploy / 🧪 QA (push) Successful in 3m20s
Build & Deploy / 🧪 Smoke Test (push) Failing after 49s
Build & Deploy / ⚡ Lighthouse (push) Successful in 3m24s
Build & Deploy / 🏗️ Build (push) Successful in 3m2s
Build & Deploy / 🚀 Deploy (push) Successful in 26s
Build & Deploy / 🔔 Notify (push) Successful in 2s
438 lines
12 KiB
Markdown
438 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
|
|
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:
|
|
- UMAMI_WEBSITE_ID=${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 $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 `UMAMI_WEBSITE_ID` environment variable:
|
|
|
|
```bash
|
|
# .env.local (not committed to git)
|
|
# 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
|