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:
UmamiScript- Loads the Umami tracking scriptAnalyticsProvider- Tracks pageviews on route changesuseAnalytics- Custom hook for tracking custom eventsanalytics-events.ts- Centralized event definitionsUmamiAnalyticsService- 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
-
Check environment variables:
echo $NEXT_PUBLIC_UMAMI_WEBSITE_ID -
Verify the script is loading:
- Open browser DevTools
- Check Network tab for
script.jsrequest - Check Console for any errors
-
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
Scriptcomponent withafterInteractivestrategy - ✅ 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:
- This README for usage examples
- The component source code for implementation details
- The Umami documentation for platform-specific questions