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
443 lines
10 KiB
Markdown
443 lines
10 KiB
Markdown
# Analytics Service Layer
|
|
|
|
This directory contains the service layer implementation for analytics tracking in the KLZ Cables application.
|
|
|
|
## Overview
|
|
|
|
The analytics service layer provides a clean abstraction over different analytics implementations (Umami, Google Analytics, etc.) while maintaining a consistent API.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
lib/services/analytics/
|
|
├── analytics-service.ts # Interface definition
|
|
├── umami-analytics-service.ts # Umami implementation
|
|
├── noop-analytics-service.ts # No-op fallback implementation
|
|
└── README.md # This file
|
|
```
|
|
|
|
## Components
|
|
|
|
### 1. AnalyticsService Interface (`analytics-service.ts`)
|
|
|
|
Defines the contract for all analytics services:
|
|
|
|
```typescript
|
|
export interface AnalyticsService {
|
|
track(eventName: string, props?: AnalyticsEventProperties): void;
|
|
trackPageview(url?: string): void;
|
|
}
|
|
```
|
|
|
|
**Key Features:**
|
|
|
|
- Type-safe event properties
|
|
- Consistent API across implementations
|
|
- Well-documented with JSDoc comments
|
|
|
|
### 2. UmamiAnalyticsService (`umami-analytics-service.ts`)
|
|
|
|
Implements the `AnalyticsService` interface for Umami analytics.
|
|
|
|
**Features:**
|
|
|
|
- Type-safe event tracking
|
|
- Automatic pageview tracking
|
|
- Browser environment detection
|
|
- Graceful error handling
|
|
- Comprehensive JSDoc documentation
|
|
|
|
**Usage:**
|
|
|
|
```typescript
|
|
import { UmamiAnalyticsService } from '@/lib/services/analytics/umami-analytics-service';
|
|
|
|
const service = new UmamiAnalyticsService({ enabled: true });
|
|
service.track('button_click', { button_id: 'cta' });
|
|
service.trackPageview('/products/123');
|
|
```
|
|
|
|
### 3. NoopAnalyticsService (`noop-analytics-service.ts`)
|
|
|
|
A no-op implementation used as a fallback when analytics are disabled.
|
|
|
|
**Features:**
|
|
|
|
- Maintains the same API as other services
|
|
- Safe to call even when analytics are disabled
|
|
- No performance impact
|
|
- Comprehensive JSDoc documentation
|
|
|
|
**Usage:**
|
|
|
|
```typescript
|
|
import { NoopAnalyticsService } from '@/lib/services/analytics/noop-analytics-service';
|
|
|
|
const service = new NoopAnalyticsService();
|
|
service.track('button_click', { button_id: 'cta' }); // Does nothing
|
|
service.trackPageview('/products/123'); // Does nothing
|
|
```
|
|
|
|
## Service Selection
|
|
|
|
The service layer automatically selects the appropriate implementation based on environment variables:
|
|
|
|
```typescript
|
|
// In lib/services/create-services.ts
|
|
const umamiEnabled = Boolean(process.env.UMAMI_WEBSITE_ID);
|
|
|
|
const analytics = umamiEnabled
|
|
? new UmamiAnalyticsService({ enabled: true })
|
|
: new NoopAnalyticsService();
|
|
```
|
|
|
|
## Environment Variables
|
|
|
|
### Required for Umami
|
|
|
|
```bash
|
|
UMAMI_WEBSITE_ID=59a7db94-0100-4c7e-98ef-99f45b17f9c3
|
|
```
|
|
|
|
### Optional (defaults provided)
|
|
|
|
```bash
|
|
NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js
|
|
```
|
|
|
|
## API Reference
|
|
|
|
### AnalyticsService Interface
|
|
|
|
#### `track(eventName: string, props?: AnalyticsEventProperties): void`
|
|
|
|
Track a custom event with optional properties.
|
|
|
|
**Parameters:**
|
|
|
|
- `eventName` - The name of the event to track
|
|
- `props` - Optional event properties (metadata)
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
service.track('product_add_to_cart', {
|
|
product_id: '123',
|
|
product_name: 'Cable',
|
|
price: 99.99,
|
|
quantity: 1,
|
|
});
|
|
```
|
|
|
|
#### `trackPageview(url?: string): void`
|
|
|
|
Track a pageview.
|
|
|
|
**Parameters:**
|
|
|
|
- `url` - The URL to track (defaults to current location)
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
// Track current page
|
|
service.trackPageview();
|
|
|
|
// Track custom URL
|
|
service.trackPageview('/products/123?category=cables');
|
|
```
|
|
|
|
### UmamiAnalyticsService
|
|
|
|
#### Constructor
|
|
|
|
```typescript
|
|
new UmamiAnalyticsService(options: UmamiAnalyticsServiceOptions)
|
|
```
|
|
|
|
**Options:**
|
|
|
|
- `enabled: boolean` - Whether analytics are enabled
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
const service = new UmamiAnalyticsService({ enabled: true });
|
|
```
|
|
|
|
### NoopAnalyticsService
|
|
|
|
#### Constructor
|
|
|
|
```typescript
|
|
new NoopAnalyticsService();
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
const service = new NoopAnalyticsService();
|
|
```
|
|
|
|
## Type Definitions
|
|
|
|
### AnalyticsEventProperties
|
|
|
|
```typescript
|
|
type AnalyticsEventProperties = Record<string, string | number | boolean | null | undefined>;
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
const properties: AnalyticsEventProperties = {
|
|
product_id: '123',
|
|
product_name: 'Cable',
|
|
price: 99.99,
|
|
quantity: 1,
|
|
in_stock: true,
|
|
discount: null,
|
|
};
|
|
```
|
|
|
|
### UmamiAnalyticsServiceOptions
|
|
|
|
```typescript
|
|
type UmamiAnalyticsServiceOptions = {
|
|
enabled: boolean;
|
|
};
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### 1. Use the Service Layer
|
|
|
|
Always use the service layer instead of calling Umami directly:
|
|
|
|
```typescript
|
|
// ✅ Good
|
|
import { getAppServices } from '@/lib/services/create-services';
|
|
|
|
const services = getAppServices();
|
|
services.analytics.track('button_click', { button_id: 'cta' });
|
|
|
|
// ❌ Avoid
|
|
(window as any).umami?.track('button_click', { button_id: 'cta' });
|
|
```
|
|
|
|
### 2. Check Environment
|
|
|
|
The service layer automatically handles environment detection:
|
|
|
|
```typescript
|
|
// ✅ Safe - works in both server and client
|
|
const services = getAppServices();
|
|
services.analytics.track('event', { prop: 'value' });
|
|
|
|
// ❌ Unsafe - may fail in server environment
|
|
if (typeof window !== 'undefined') {
|
|
window.umami?.track('event', { prop: 'value' });
|
|
}
|
|
```
|
|
|
|
### 3. Use Type-Safe Events
|
|
|
|
Import events from the centralized definitions:
|
|
|
|
```typescript
|
|
import { AnalyticsEvents } from '@/components/analytics/analytics-events';
|
|
|
|
// ✅ Type-safe
|
|
services.analytics.track(AnalyticsEvents.BUTTON_CLICK, {
|
|
button_id: 'cta',
|
|
});
|
|
|
|
// ❌ Prone to typos
|
|
services.analytics.track('button_click', {
|
|
button_id: 'cta',
|
|
});
|
|
```
|
|
|
|
### 4. Handle Disabled Analytics
|
|
|
|
The service layer gracefully handles disabled analytics:
|
|
|
|
```typescript
|
|
// When UMAMI_WEBSITE_ID is not set:
|
|
// - NoopAnalyticsService is used
|
|
// - All calls are safe (no-op)
|
|
// - No errors are thrown
|
|
|
|
const services = getAppServices();
|
|
services.analytics.track('event', { prop: 'value' }); // Safe, does nothing
|
|
```
|
|
|
|
## Testing
|
|
|
|
### Mocking for Tests
|
|
|
|
```typescript
|
|
// __tests__/analytics-mock.ts
|
|
export const mockAnalytics = {
|
|
track: jest.fn(),
|
|
trackPageview: jest.fn(),
|
|
};
|
|
|
|
jest.mock('@/lib/services/create-services', () => ({
|
|
getAppServices: () => ({
|
|
analytics: mockAnalytics,
|
|
}),
|
|
}));
|
|
|
|
// Usage in tests
|
|
import { mockAnalytics } from './analytics-mock';
|
|
|
|
test('tracks button click', () => {
|
|
// ... test code ...
|
|
expect(mockAnalytics.track).toHaveBeenCalledWith('button_click', {
|
|
button_id: 'cta',
|
|
});
|
|
});
|
|
```
|
|
|
|
### Development Mode
|
|
|
|
In development, the service layer logs to console:
|
|
|
|
```bash
|
|
# Console output:
|
|
[Umami] Tracked event: button_click { button_id: 'cta' }
|
|
[Umami] Tracked pageview: /products/123
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
The service layer includes built-in error handling:
|
|
|
|
1. **Environment Detection** - Checks for browser environment
|
|
2. **Service Availability** - Checks if Umami is loaded
|
|
3. **Graceful Degradation** - Falls back to NoopAnalyticsService if needed
|
|
|
|
```typescript
|
|
// These are all safe:
|
|
const services = getAppServices();
|
|
services.analytics.track('event', { prop: 'value' }); // Works or does nothing
|
|
services.analytics.trackPageview('/path'); // Works or does nothing
|
|
```
|
|
|
|
## Performance
|
|
|
|
### Singleton Pattern
|
|
|
|
The service layer uses a singleton pattern for performance:
|
|
|
|
```typescript
|
|
// First call creates the singleton
|
|
const services1 = getAppServices();
|
|
|
|
// Subsequent calls return the cached singleton
|
|
const services2 = getAppServices();
|
|
|
|
// services1 === services2 (same instance)
|
|
```
|
|
|
|
### Lazy Initialization
|
|
|
|
Services are only created when first accessed:
|
|
|
|
```typescript
|
|
// Services are not created until getAppServices() is called
|
|
// This keeps initial bundle size minimal
|
|
```
|
|
|
|
## Integration with Components
|
|
|
|
### Client Components
|
|
|
|
```typescript
|
|
'use client';
|
|
|
|
import { getAppServices } from '@/lib/services/create-services';
|
|
|
|
function MyComponent() {
|
|
const handleClick = () => {
|
|
const services = getAppServices();
|
|
services.analytics.track('button_click', { button_id: 'my-button' });
|
|
};
|
|
|
|
return <button onClick={handleClick}>Click Me</button>;
|
|
}
|
|
```
|
|
|
|
### Server Components
|
|
|
|
```typescript
|
|
import { getAppServices } from '@/lib/services/create-services';
|
|
|
|
async function MyServerComponent() {
|
|
const services = getAppServices();
|
|
|
|
// Note: Analytics won't work in server components
|
|
// Use client components for analytics tracking
|
|
// But you can still access other services like cache
|
|
|
|
const data = await services.cache.get('key');
|
|
|
|
return <div>{data}</div>;
|
|
}
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Analytics Not Working
|
|
|
|
1. **Check environment variables:**
|
|
|
|
```bash
|
|
echo $UMAMI_WEBSITE_ID
|
|
```
|
|
|
|
2. **Verify service selection:**
|
|
|
|
```typescript
|
|
import { getAppServices } from '@/lib/services/create-services';
|
|
|
|
const services = getAppServices();
|
|
console.log(services.analytics); // Should be UmamiAnalyticsService
|
|
```
|
|
|
|
3. **Check Umami dashboard:**
|
|
- Log into Umami
|
|
- Verify website ID matches
|
|
- Check if data is being received
|
|
|
|
### Common Issues
|
|
|
|
| Issue | Solution |
|
|
| ------------------- | ----------------------------------- |
|
|
| No data in Umami | Check website ID and script URL |
|
|
| Events not tracking | Verify service is being used |
|
|
| Script not loading | Check network connection, CORS |
|
|
| Wrong data | Verify event properties are correct |
|
|
|
|
## Related Files
|
|
|
|
- [`components/analytics/useAnalytics.ts`](../components/analytics/useAnalytics.ts) - Custom hook for easy event tracking
|
|
- [`components/analytics/analytics-events.ts`](../components/analytics/analytics-events.ts) - Event definitions
|
|
- [`components/analytics/UmamiScript.tsx`](../components/analytics/UmamiScript.tsx) - Script loader component
|
|
- [`components/analytics/AnalyticsProvider.tsx`](../components/analytics/AnalyticsProvider.tsx) - Route change tracker
|
|
- [`lib/services/create-services.ts`](../lib/services/create-services.ts) - Service factory
|
|
|
|
## Summary
|
|
|
|
The analytics service layer provides:
|
|
|
|
- ✅ **Type-safe API** - TypeScript throughout
|
|
- ✅ **Clean abstraction** - Easy to switch analytics providers
|
|
- ✅ **Graceful degradation** - Safe no-op fallback
|
|
- ✅ **Comprehensive documentation** - JSDoc comments and examples
|
|
- ✅ **Performance optimized** - Singleton pattern, lazy initialization
|
|
- ✅ **Error handling** - Safe in all environments
|
|
|
|
This layer is the foundation for all analytics tracking in the application.
|