Next.js Supabase V3 (#463)
Version 3 of the kit: - Radix UI replaced with Base UI (using the Shadcn UI patterns) - next-intl replaces react-i18next - enhanceAction deprecated; usage moved to next-safe-action - main layout now wrapped with [locale] path segment - Teams only mode - Layout updates - Zod v4 - Next.js 16.2 - Typescript 6 - All other dependencies updated - Removed deprecated Edge CSRF - Dynamic Github Action runner
This commit is contained in:
committed by
GitHub
parent
4912e402a3
commit
7ebff31475
363
docs/analytics/analytics-and-events.mdoc
Normal file
363
docs/analytics/analytics-and-events.mdoc
Normal file
@@ -0,0 +1,363 @@
|
||||
---
|
||||
status: "published"
|
||||
title: 'Understanding Analytics and App Events in MakerKit'
|
||||
label: 'Analytics and Events'
|
||||
description: 'Learn how the Analytics and App Events systems work together to provide centralized, maintainable event tracking in your MakerKit SaaS application.'
|
||||
order: 0
|
||||
---
|
||||
|
||||
MakerKit separates event emission from analytics tracking through two interconnected systems: **App Events** for broadcasting important occurrences in your app, and **Analytics** for tracking user behavior. This separation keeps your components clean and your analytics logic centralized.
|
||||
|
||||
## Why Centralized Analytics
|
||||
|
||||
Scattering `analytics.trackEvent()` calls throughout your codebase creates maintenance problems. When you need to change event names, add properties, or switch providers, you hunt through dozens of files.
|
||||
|
||||
The centralized approach solves this:
|
||||
|
||||
```typescript
|
||||
// Instead of this (scattered analytics)
|
||||
function CheckoutButton() {
|
||||
const handleClick = () => {
|
||||
analytics.trackEvent('checkout_started', { plan: 'pro' });
|
||||
analytics.identify(userId);
|
||||
mixpanel.track('Checkout Started');
|
||||
// More provider-specific code...
|
||||
};
|
||||
}
|
||||
|
||||
// Do this (centralized via App Events)
|
||||
function CheckoutButton() {
|
||||
const { emit } = useAppEvents();
|
||||
|
||||
const handleClick = () => {
|
||||
emit({ type: 'checkout.started', payload: { planId: 'pro' } });
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
The analytics mapping lives in one place: `apps/web/components/analytics-provider.tsx`.
|
||||
|
||||
## How It Works
|
||||
|
||||
The system has three parts:
|
||||
|
||||
1. **App Events Provider**: A React Context that provides `emit`, `on`, and `off` functions
|
||||
2. **Analytics Provider**: Subscribes to App Events and maps them to analytics calls
|
||||
3. **Analytics Manager**: Dispatches events to all registered analytics providers
|
||||
|
||||
```
|
||||
Component Analytics Provider Providers
|
||||
│ │ │
|
||||
│ emit('checkout.started') │ │
|
||||
│────────────────────────────▶│ │
|
||||
│ │ analytics.trackEvent │
|
||||
│ │────────────────────────▶│
|
||||
│ │ analytics.identify │
|
||||
│ │────────────────────────▶│
|
||||
```
|
||||
|
||||
## Emitting Events
|
||||
|
||||
Use the `useAppEvents` hook to emit events from any component:
|
||||
|
||||
```typescript
|
||||
import { useAppEvents } from '@kit/shared/events';
|
||||
|
||||
function FeatureButton() {
|
||||
const { emit } = useAppEvents();
|
||||
|
||||
const handleClick = () => {
|
||||
emit({
|
||||
type: 'feature.used',
|
||||
payload: { featureName: 'export' }
|
||||
});
|
||||
};
|
||||
|
||||
return <button onClick={handleClick}>Export</button>;
|
||||
}
|
||||
```
|
||||
|
||||
The event is broadcast to all listeners, including the analytics provider.
|
||||
|
||||
## Default Event Types
|
||||
|
||||
MakerKit defines these base event types in `@kit/shared/events`:
|
||||
|
||||
```typescript
|
||||
interface BaseAppEventTypes {
|
||||
'user.signedIn': { userId: string };
|
||||
'user.signedUp': { method: 'magiclink' | 'password' };
|
||||
'user.updated': Record<string, never>;
|
||||
'checkout.started': { planId: string; account?: string };
|
||||
}
|
||||
```
|
||||
|
||||
These events are emitted automatically by MakerKit components and mapped to analytics calls.
|
||||
|
||||
### Event Descriptions
|
||||
|
||||
| Event | When Emitted | Analytics Action |
|
||||
|-------|--------------|------------------|
|
||||
| `user.signedIn` | After successful login | `identify(userId)` |
|
||||
| `user.signedUp` | After registration | `trackEvent('user.signedUp')` |
|
||||
| `user.updated` | After profile update | `trackEvent('user.updated')` |
|
||||
| `checkout.started` | When billing checkout begins | `trackEvent('checkout.started')` |
|
||||
|
||||
**Note**: The `user.signedUp` event does not fire automatically for social/OAuth signups. You may need to emit it manually in your OAuth callback handler.
|
||||
|
||||
## Creating Custom Events
|
||||
|
||||
Define custom events by extending `ConsumerProvidedEventTypes`:
|
||||
|
||||
```typescript {% title="lib/events/custom-events.ts" %}
|
||||
import { ConsumerProvidedEventTypes } from '@kit/shared/events';
|
||||
|
||||
export interface MyAppEvents extends ConsumerProvidedEventTypes {
|
||||
'feature.used': { featureName: string; duration?: number };
|
||||
'project.created': { projectId: string; template: string };
|
||||
'export.completed': { format: 'csv' | 'json' | 'pdf'; rowCount: number };
|
||||
}
|
||||
```
|
||||
|
||||
Use the typed hook in your components:
|
||||
|
||||
```typescript
|
||||
import { useAppEvents } from '@kit/shared/events';
|
||||
import type { MyAppEvents } from '~/lib/events/custom-events';
|
||||
|
||||
function ProjectForm() {
|
||||
const { emit } = useAppEvents<MyAppEvents>();
|
||||
|
||||
const handleCreate = (project: Project) => {
|
||||
emit({
|
||||
type: 'project.created',
|
||||
payload: {
|
||||
projectId: project.id,
|
||||
template: project.template,
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
TypeScript enforces the correct payload shape for each event type.
|
||||
|
||||
## Mapping Events to Analytics
|
||||
|
||||
The `AnalyticsProvider` component maps events to analytics calls. Add your custom events here:
|
||||
|
||||
```typescript {% title="apps/web/components/analytics-provider.tsx" %}
|
||||
const analyticsMapping: AnalyticsMapping = {
|
||||
'user.signedIn': (event) => {
|
||||
const { userId, ...traits } = event.payload;
|
||||
if (userId) {
|
||||
return analytics.identify(userId, traits);
|
||||
}
|
||||
},
|
||||
'user.signedUp': (event) => {
|
||||
return analytics.trackEvent(event.type, event.payload);
|
||||
},
|
||||
'checkout.started': (event) => {
|
||||
return analytics.trackEvent(event.type, event.payload);
|
||||
},
|
||||
// Add custom event mappings
|
||||
'feature.used': (event) => {
|
||||
return analytics.trackEvent('Feature Used', {
|
||||
feature_name: event.payload.featureName,
|
||||
duration: String(event.payload.duration ?? 0),
|
||||
});
|
||||
},
|
||||
'project.created': (event) => {
|
||||
return analytics.trackEvent('Project Created', {
|
||||
project_id: event.payload.projectId,
|
||||
template: event.payload.template,
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
This is the only place you need to modify when changing analytics behavior.
|
||||
|
||||
## Listening to Events
|
||||
|
||||
Beyond analytics, you can subscribe to events for other purposes:
|
||||
|
||||
```typescript
|
||||
import { useAppEvents } from '@kit/shared/events';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
function NotificationListener() {
|
||||
const { on, off } = useAppEvents();
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (event) => {
|
||||
showToast(`Project ${event.payload.projectId} created!`);
|
||||
};
|
||||
|
||||
on('project.created', handler);
|
||||
return () => off('project.created', handler);
|
||||
}, [on, off]);
|
||||
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
This pattern is useful for triggering side effects like notifications, confetti animations, or feature tours.
|
||||
|
||||
## Direct Analytics API
|
||||
|
||||
While centralized events are recommended, you can use the analytics API directly when needed:
|
||||
|
||||
```typescript
|
||||
import { analytics } from '@kit/analytics';
|
||||
|
||||
// Identify a user
|
||||
void analytics.identify('user_123', {
|
||||
email: 'user@example.com',
|
||||
plan: 'pro',
|
||||
});
|
||||
|
||||
// Track an event
|
||||
void analytics.trackEvent('Button Clicked', {
|
||||
button: 'submit',
|
||||
page: 'settings',
|
||||
});
|
||||
|
||||
// Track a page view (usually automatic)
|
||||
void analytics.trackPageView('/dashboard');
|
||||
```
|
||||
|
||||
Use direct calls for one-off tracking that does not warrant an event type.
|
||||
|
||||
## Automatic Page View Tracking
|
||||
|
||||
The `AnalyticsProvider` automatically tracks page views when the Next.js route changes:
|
||||
|
||||
```typescript
|
||||
// This happens automatically in AnalyticsProvider
|
||||
function useReportPageView(reportFn: (url: string) => unknown) {
|
||||
const pathname = usePathname();
|
||||
|
||||
useEffect(() => {
|
||||
const url = pathname;
|
||||
reportFn(url);
|
||||
}, [pathname]);
|
||||
}
|
||||
```
|
||||
|
||||
You do not need to manually track page views unless you have a specific use case.
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Track Form Submissions
|
||||
|
||||
```typescript
|
||||
function ContactForm() {
|
||||
const { emit } = useAppEvents<MyAppEvents>();
|
||||
|
||||
const handleSubmit = async (data: FormData) => {
|
||||
await submitForm(data);
|
||||
|
||||
emit({
|
||||
type: 'form.submitted',
|
||||
payload: { formName: 'contact', fields: Object.keys(data).length },
|
||||
});
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Track Feature Engagement
|
||||
|
||||
```typescript
|
||||
function AIAssistant() {
|
||||
const { emit } = useAppEvents<MyAppEvents>();
|
||||
const startTime = useRef<number>();
|
||||
|
||||
const handleOpen = () => {
|
||||
startTime.current = Date.now();
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
const duration = Date.now() - (startTime.current ?? Date.now());
|
||||
|
||||
emit({
|
||||
type: 'feature.used',
|
||||
payload: { featureName: 'ai-assistant', duration },
|
||||
});
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Track Errors
|
||||
|
||||
```typescript
|
||||
function ErrorBoundary({ children }) {
|
||||
const { emit } = useAppEvents<MyAppEvents>();
|
||||
|
||||
const handleError = (error: Error) => {
|
||||
emit({
|
||||
type: 'error.occurred',
|
||||
payload: {
|
||||
message: error.message,
|
||||
stack: error.stack?.slice(0, 500),
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Debugging Events
|
||||
|
||||
During development, add logging to your event handlers to verify events are emitting correctly:
|
||||
|
||||
```typescript {% title="apps/web/components/analytics-provider.tsx" %}
|
||||
const analyticsMapping: AnalyticsMapping = {
|
||||
'user.signedIn': (event) => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('[Analytics Event]', event.type, event.payload);
|
||||
}
|
||||
|
||||
const { userId, ...traits } = event.payload;
|
||||
if (userId) {
|
||||
return analytics.identify(userId, traits);
|
||||
}
|
||||
},
|
||||
'checkout.started': (event) => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('[Analytics Event]', event.type, event.payload);
|
||||
}
|
||||
|
||||
return analytics.trackEvent(event.type, event.payload);
|
||||
},
|
||||
// Add logging to other handlers as needed
|
||||
};
|
||||
```
|
||||
|
||||
You can also use your analytics provider's debug mode (PostHog and GA4 both offer live event views in their dashboards).
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use App Events for domain events**: Business-relevant events (signup, purchase, feature use) should go through App Events
|
||||
2. **Keep payloads minimal**: Only include data you will actually analyze
|
||||
3. **Use consistent naming**: Follow a pattern like `noun.verb` (user.signedUp, project.created)
|
||||
4. **Type your events**: Define interfaces for compile-time safety
|
||||
5. **Test event emission**: Verify critical events emit during integration tests
|
||||
6. **Document your events**: Maintain a list of events and their purposes
|
||||
|
||||
{% faq
|
||||
title="Frequently Asked Questions"
|
||||
items=[
|
||||
{"question": "Can I use analytics without App Events?", "answer": "Yes. Import analytics from @kit/analytics and call trackEvent directly. However, the centralized approach through App Events is easier to maintain as your application grows."},
|
||||
{"question": "How do I track events on the server side?", "answer": "Import analytics from @kit/analytics/server. Note that only PostHog supports server-side analytics out of the box. The App Events system is client-side only."},
|
||||
{"question": "Are page views tracked automatically?", "answer": "Yes. The AnalyticsProvider component tracks page views whenever the Next.js route changes. You only need manual tracking for virtual page views in SPAs."},
|
||||
{"question": "How do I debug which events are firing?", "answer": "Add a wildcard handler in your analytics mapping that logs events in development mode. You can also use browser DevTools or your analytics provider's debug mode."},
|
||||
{"question": "Can I emit events from Server Components?", "answer": "No. App Events use React Context which requires a client component. Emit events from client components or use the server-side analytics API directly."},
|
||||
{"question": "What happens if no analytics provider is configured?", "answer": "Events dispatch to the NullAnalyticsService which silently ignores them. Your application continues to work without errors."}
|
||||
]
|
||||
/%}
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Set up Google Analytics](google-analytics-provider) for marketing analytics
|
||||
- [Set up PostHog](posthog-analytics-provider) for product analytics with feature flags
|
||||
- [Create a custom provider](custom-analytics-provider) to integrate other services
|
||||
360
docs/analytics/custom-analytics-provider.mdoc
Normal file
360
docs/analytics/custom-analytics-provider.mdoc
Normal file
@@ -0,0 +1,360 @@
|
||||
---
|
||||
status: "published"
|
||||
title: 'Creating a Custom Analytics Provider in MakerKit'
|
||||
label: 'Custom Analytics Provider'
|
||||
description: 'Build a custom analytics provider to integrate Mixpanel, Amplitude, Segment, or any analytics service with MakerKit unified analytics API.'
|
||||
order: 5
|
||||
---
|
||||
|
||||
MakerKit's analytics system is provider-agnostic. If your preferred analytics service is not included (Google Analytics, PostHog, Umami), you can create a custom provider that integrates with the unified analytics API. Events dispatched through `analytics.trackEvent()` or App Events will automatically route to your custom provider alongside any other registered providers.
|
||||
|
||||
## The AnalyticsService Interface
|
||||
|
||||
Every analytics provider must implement the `AnalyticsService` interface:
|
||||
|
||||
```typescript
|
||||
interface AnalyticsService {
|
||||
initialize(): Promise<unknown>;
|
||||
identify(userId: string, traits?: Record<string, string>): Promise<unknown>;
|
||||
trackPageView(path: string): Promise<unknown>;
|
||||
trackEvent(
|
||||
eventName: string,
|
||||
eventProperties?: Record<string, string | string[]>
|
||||
): Promise<unknown>;
|
||||
}
|
||||
```
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `initialize()` | Load scripts, set up the SDK |
|
||||
| `identify()` | Associate a user ID with subsequent events |
|
||||
| `trackPageView()` | Record a page view |
|
||||
| `trackEvent()` | Record a custom event with properties |
|
||||
|
||||
All methods return Promises. Use `void` when calling from non-async contexts.
|
||||
|
||||
## Example: Mixpanel Provider
|
||||
|
||||
Here is a complete implementation for Mixpanel:
|
||||
|
||||
```typescript {% title="packages/analytics/src/mixpanel-service.ts" %}
|
||||
import { NullAnalyticsService } from './null-analytics-service';
|
||||
import type { AnalyticsService } from './types';
|
||||
|
||||
class MixpanelService implements AnalyticsService {
|
||||
private mixpanel: typeof import('mixpanel-browser') | null = null;
|
||||
private token: string;
|
||||
|
||||
constructor(token: string) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const mixpanel = await import('mixpanel-browser');
|
||||
mixpanel.init(this.token, {
|
||||
track_pageview: false, // We handle this manually
|
||||
persistence: 'localStorage',
|
||||
});
|
||||
|
||||
this.mixpanel = mixpanel;
|
||||
}
|
||||
|
||||
async identify(userId: string, traits?: Record<string, string>): Promise<void> {
|
||||
if (!this.mixpanel) return;
|
||||
|
||||
this.mixpanel.identify(userId);
|
||||
|
||||
if (traits) {
|
||||
this.mixpanel.people.set(traits);
|
||||
}
|
||||
}
|
||||
|
||||
async trackPageView(path: string): Promise<void> {
|
||||
if (!this.mixpanel) return;
|
||||
|
||||
this.mixpanel.track('Page Viewed', { path });
|
||||
}
|
||||
|
||||
async trackEvent(
|
||||
eventName: string,
|
||||
eventProperties?: Record<string, string | string[]>
|
||||
): Promise<void> {
|
||||
if (!this.mixpanel) return;
|
||||
|
||||
this.mixpanel.track(eventName, eventProperties);
|
||||
}
|
||||
}
|
||||
|
||||
export function createMixpanelService(): AnalyticsService {
|
||||
const token = process.env.NEXT_PUBLIC_MIXPANEL_TOKEN;
|
||||
|
||||
if (!token) {
|
||||
console.warn('Mixpanel token not configured');
|
||||
return new NullAnalyticsService();
|
||||
}
|
||||
|
||||
return new MixpanelService(token);
|
||||
}
|
||||
```
|
||||
|
||||
Install the Mixpanel SDK:
|
||||
|
||||
```bash
|
||||
pnpm add mixpanel-browser --filter "@kit/analytics"
|
||||
```
|
||||
|
||||
## Registering Your Provider
|
||||
|
||||
Add your custom provider to the analytics manager:
|
||||
|
||||
```typescript {% title="packages/analytics/src/index.ts" %}
|
||||
import { createAnalyticsManager } from './analytics-manager';
|
||||
import { createMixpanelService } from './mixpanel-service';
|
||||
import type { AnalyticsManager } from './types';
|
||||
|
||||
export const analytics: AnalyticsManager = createAnalyticsManager({
|
||||
providers: {
|
||||
mixpanel: createMixpanelService,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Add environment variables:
|
||||
|
||||
```bash {% title=".env.local" %}
|
||||
NEXT_PUBLIC_MIXPANEL_TOKEN=your_mixpanel_token
|
||||
```
|
||||
|
||||
## Using Multiple Providers
|
||||
|
||||
Register multiple providers to dispatch events to all of them:
|
||||
|
||||
```typescript {% title="packages/analytics/src/index.ts" %}
|
||||
import { createAnalyticsManager } from './analytics-manager';
|
||||
import { createMixpanelService } from './mixpanel-service';
|
||||
import { createPostHogAnalyticsService } from '@kit/posthog/client';
|
||||
|
||||
export const analytics = createAnalyticsManager({
|
||||
providers: {
|
||||
mixpanel: createMixpanelService,
|
||||
posthog: createPostHogAnalyticsService,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
When you call `analytics.trackEvent()`, both Mixpanel and PostHog receive the event.
|
||||
|
||||
## Example: Amplitude Provider
|
||||
|
||||
Here is a skeleton for Amplitude:
|
||||
|
||||
```typescript {% title="packages/analytics/src/amplitude-service.ts" %}
|
||||
import type { AnalyticsService } from './types';
|
||||
|
||||
class AmplitudeService implements AnalyticsService {
|
||||
private amplitude: typeof import('@amplitude/analytics-browser') | null = null;
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const amplitude = await import('@amplitude/analytics-browser');
|
||||
const apiKey = process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY;
|
||||
|
||||
if (apiKey) {
|
||||
amplitude.init(apiKey);
|
||||
this.amplitude = amplitude;
|
||||
}
|
||||
}
|
||||
|
||||
async identify(userId: string, traits?: Record<string, string>): Promise<void> {
|
||||
if (!this.amplitude) return;
|
||||
|
||||
this.amplitude.setUserId(userId);
|
||||
|
||||
if (traits) {
|
||||
const identifyEvent = new this.amplitude.Identify();
|
||||
Object.entries(traits).forEach(([key, value]) => {
|
||||
identifyEvent.set(key, value);
|
||||
});
|
||||
this.amplitude.identify(identifyEvent);
|
||||
}
|
||||
}
|
||||
|
||||
async trackPageView(path: string): Promise<void> {
|
||||
if (!this.amplitude) return;
|
||||
this.amplitude.track('Page Viewed', { path });
|
||||
}
|
||||
|
||||
async trackEvent(
|
||||
eventName: string,
|
||||
eventProperties?: Record<string, string | string[]>
|
||||
): Promise<void> {
|
||||
if (!this.amplitude) return;
|
||||
this.amplitude.track(eventName, eventProperties);
|
||||
}
|
||||
}
|
||||
|
||||
export function createAmplitudeService(): AnalyticsService {
|
||||
return new AmplitudeService();
|
||||
}
|
||||
```
|
||||
|
||||
## Example: Segment Provider
|
||||
|
||||
Segment acts as a data router to multiple destinations:
|
||||
|
||||
```typescript {% title="packages/analytics/src/segment-service.ts" %}
|
||||
import type { AnalyticsService } from './types';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
analytics: {
|
||||
identify: (userId: string, traits?: object) => void;
|
||||
page: (name?: string, properties?: object) => void;
|
||||
track: (event: string, properties?: object) => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class SegmentService implements AnalyticsService {
|
||||
async initialize(): Promise<void> {
|
||||
// Segment snippet is typically added via <Script> in layout
|
||||
// This method can verify it's loaded
|
||||
if (typeof window === 'undefined' || !window.analytics) {
|
||||
console.warn('Segment analytics not loaded');
|
||||
}
|
||||
}
|
||||
|
||||
async identify(userId: string, traits?: Record<string, string>): Promise<void> {
|
||||
window.analytics?.identify(userId, traits);
|
||||
}
|
||||
|
||||
async trackPageView(path: string): Promise<void> {
|
||||
window.analytics?.page(undefined, { path });
|
||||
}
|
||||
|
||||
async trackEvent(
|
||||
eventName: string,
|
||||
eventProperties?: Record<string, string | string[]>
|
||||
): Promise<void> {
|
||||
window.analytics?.track(eventName, eventProperties);
|
||||
}
|
||||
}
|
||||
|
||||
export function createSegmentService(): AnalyticsService {
|
||||
return new SegmentService();
|
||||
}
|
||||
```
|
||||
|
||||
## Server-Side Providers
|
||||
|
||||
For server-side analytics, create a separate service file:
|
||||
|
||||
```typescript {% title="packages/analytics/src/mixpanel-server.ts" %}
|
||||
import Mixpanel from 'mixpanel';
|
||||
import type { AnalyticsService } from './types';
|
||||
|
||||
class MixpanelServerService implements AnalyticsService {
|
||||
private mixpanel: Mixpanel.Mixpanel | null = null;
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
const token = process.env.MIXPANEL_TOKEN; // Note: no NEXT_PUBLIC_ prefix
|
||||
if (token) {
|
||||
this.mixpanel = Mixpanel.init(token);
|
||||
}
|
||||
}
|
||||
|
||||
async identify(userId: string, traits?: Record<string, string>): Promise<void> {
|
||||
if (!this.mixpanel || !traits) return;
|
||||
this.mixpanel.people.set(userId, traits);
|
||||
}
|
||||
|
||||
async trackPageView(path: string): Promise<void> {
|
||||
// Server-side page views are uncommon
|
||||
}
|
||||
|
||||
async trackEvent(
|
||||
eventName: string,
|
||||
eventProperties?: Record<string, string | string[]>
|
||||
): Promise<void> {
|
||||
if (!this.mixpanel) return;
|
||||
this.mixpanel.track(eventName, eventProperties);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Register in `packages/analytics/src/server.ts`:
|
||||
|
||||
```typescript {% title="packages/analytics/src/server.ts" %}
|
||||
import 'server-only';
|
||||
|
||||
import { createAnalyticsManager } from './analytics-manager';
|
||||
import { createMixpanelServerService } from './mixpanel-server';
|
||||
|
||||
export const analytics = createAnalyticsManager({
|
||||
providers: {
|
||||
mixpanel: createMixpanelServerService,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## The NullAnalyticsService
|
||||
|
||||
When no providers are configured, MakerKit uses a null service that silently ignores all calls:
|
||||
|
||||
```typescript
|
||||
const NullAnalyticsService: AnalyticsService = {
|
||||
initialize: () => Promise.resolve(),
|
||||
identify: () => Promise.resolve(),
|
||||
trackPageView: () => Promise.resolve(),
|
||||
trackEvent: () => Promise.resolve(),
|
||||
};
|
||||
```
|
||||
|
||||
Your provider factory can return this when misconfigured to avoid errors.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Dynamic imports**: Load SDKs dynamically to reduce bundle size
|
||||
2. **Environment checks**: Always check `typeof window` before accessing browser APIs
|
||||
3. **Graceful degradation**: Return early if the SDK fails to load
|
||||
4. **Typed properties**: Define TypeScript interfaces for your event properties
|
||||
5. **Consistent naming**: Use the same event names across all providers
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Provider not receiving events
|
||||
|
||||
- Verify the provider is registered in `createAnalyticsManager`
|
||||
- Check that `initialize()` completes without errors
|
||||
- Confirm environment variables are set
|
||||
|
||||
### TypeScript errors
|
||||
|
||||
- Ensure your class implements all methods in `AnalyticsService`
|
||||
- Check that return types are `Promise<unknown>` or more specific
|
||||
|
||||
### Events delayed or missing
|
||||
|
||||
- Some providers batch events. Check provider-specific settings
|
||||
- Verify the provider SDK is loaded before events are sent
|
||||
|
||||
{% faq
|
||||
title="Frequently Asked Questions"
|
||||
items=[
|
||||
{"question": "Can I use the same provider for client and server?", "answer": "It depends on the SDK. Some analytics SDKs (like PostHog) offer both client and server versions. Others (like Mixpanel) have separate packages. Create separate service files for each environment."},
|
||||
{"question": "How do I test my custom provider?", "answer": "Add console.log statements in each method during development. Most analytics dashboards also have a debug or live events view."},
|
||||
{"question": "Can I conditionally load providers?", "answer": "Yes. Your factory function can check environment variables or feature flags and return NullAnalyticsService when the provider should be disabled."},
|
||||
{"question": "How do I handle errors in providers?", "answer": "Wrap SDK calls in try-catch blocks. Log errors but do not throw them, as this would affect other providers in the chain."}
|
||||
]
|
||||
/%}
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Learn about Analytics and Events](analytics-and-events) for event patterns
|
||||
- [See Google Analytics](google-analytics-provider) as a reference implementation
|
||||
- [Try PostHog](posthog-analytics-provider) for a full-featured option
|
||||
148
docs/analytics/google-analytics-provider.mdoc
Normal file
148
docs/analytics/google-analytics-provider.mdoc
Normal file
@@ -0,0 +1,148 @@
|
||||
---
|
||||
status: "published"
|
||||
title: 'Using the Google Analytics Provider in Next.js Supabase Turbo'
|
||||
label: 'Google Analytics'
|
||||
description: 'Add Google Analytics 4 (GA4) to your MakerKit application for page views, user tracking, and conversion measurement.'
|
||||
order: 2
|
||||
---
|
||||
|
||||
Google Analytics 4 (GA4) provides web analytics focused on marketing attribution, conversion tracking, and audience insights. Use it when your marketing team needs Google's ecosystem for ad optimization and reporting.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before starting, you need:
|
||||
|
||||
- A Google Analytics 4 property ([create one here](https://analytics.google.com/))
|
||||
- Your GA4 Measurement ID (format: `G-XXXXXXXXXX`)
|
||||
|
||||
Find your Measurement ID in GA4: **Admin > Data Streams > Select your stream > Measurement ID**.
|
||||
|
||||
## Installation
|
||||
|
||||
Install the Google Analytics plugin using the MakerKit CLI:
|
||||
|
||||
```bash
|
||||
npx @makerkit/cli@latest plugins add google-analytics
|
||||
```
|
||||
|
||||
Our codemod will wire up the plugin in your project, so you don't have to do anything manually. Please review the changes with `git diff`.
|
||||
|
||||
Please add your Measurement ID to environment variables:
|
||||
|
||||
```bash {% title="apps/web/.env.local" %}
|
||||
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `NEXT_PUBLIC_GA_MEASUREMENT_ID` | Yes | Your GA4 Measurement ID |
|
||||
| `NEXT_PUBLIC_GA_DISABLE_PAGE_VIEWS_TRACKING` | No | Set to `true` to disable automatic page view tracking |
|
||||
| `NEXT_PUBLIC_GA_DISABLE_LOCALHOST_TRACKING` | No | Set to `true` to disable tracking on localhost |
|
||||
|
||||
### Development Configuration
|
||||
|
||||
Disable localhost tracking to avoid polluting your analytics during development:
|
||||
|
||||
```bash {% title=".env.local" %}
|
||||
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
|
||||
NEXT_PUBLIC_GA_DISABLE_LOCALHOST_TRACKING=true
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
After configuration, verify the integration:
|
||||
|
||||
1. Open your application in the browser
|
||||
2. Open Chrome DevTools > Network tab
|
||||
3. Filter by `google-analytics` or `gtag`
|
||||
4. Navigate between pages and confirm requests are sent
|
||||
5. Check GA4 Realtime reports to see your session
|
||||
|
||||
## Using with Other Providers
|
||||
|
||||
Google Analytics can run alongside other providers. Events dispatch to all registered providers:
|
||||
|
||||
```typescript {% title="packages/analytics/src/index.ts" %}
|
||||
import { createGoogleAnalyticsService } from '@kit/google-analytics';
|
||||
import { createPostHogAnalyticsService } from '@kit/posthog/client';
|
||||
|
||||
import { createAnalyticsManager } from './analytics-manager';
|
||||
|
||||
export const analytics = createAnalyticsManager({
|
||||
providers: {
|
||||
'google-analytics': createGoogleAnalyticsService,
|
||||
posthog: createPostHogAnalyticsService,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
This setup is common when marketing uses GA4 for attribution while product uses PostHog for behavior analysis.
|
||||
|
||||
## Tracked Events
|
||||
|
||||
With the default configuration, Google Analytics receives:
|
||||
|
||||
- **Page views**: Automatically tracked on route changes
|
||||
- **User identification**: When `analytics.identify()` is called
|
||||
- **Custom events**: All events passed to `analytics.trackEvent()`
|
||||
|
||||
Events from the App Events system (user.signedUp, checkout.started, etc.) are forwarded to GA4 through the analytics mapping.
|
||||
|
||||
## GDPR Considerations
|
||||
|
||||
Google Analytics sets cookies and requires user consent in the EU. Integrate with the [Cookie Banner component](/docs/next-supabase-turbo/components/cookie-banner) to manage consent:
|
||||
|
||||
```typescript
|
||||
import { useCookieConsent, ConsentStatus } from '@kit/ui/cookie-banner';
|
||||
import { analytics } from '@kit/analytics';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
function AnalyticsGate({ children }) {
|
||||
const { status } = useCookieConsent();
|
||||
|
||||
useEffect(() => {
|
||||
if (status === ConsentStatus.Accepted) {
|
||||
// GA is initialized automatically when consent is given
|
||||
// You may want to delay initialization until consent
|
||||
}
|
||||
}, [status]);
|
||||
|
||||
return children;
|
||||
}
|
||||
```
|
||||
|
||||
Consider using [Umami](umami-analytics-provider) for cookie-free, GDPR-compliant analytics.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Events not appearing in GA4
|
||||
|
||||
- Verify your Measurement ID is correct
|
||||
- Check that `NEXT_PUBLIC_GA_DISABLE_LOCALHOST_TRACKING` is not `true` in production
|
||||
- GA4 has a delay of up to 24-48 hours for some reports. Use Realtime for immediate verification
|
||||
|
||||
### Duplicate page views
|
||||
|
||||
- Ensure you have not called `trackPageView` manually. MakerKit tracks page views automatically
|
||||
|
||||
### Ad blockers
|
||||
|
||||
- Ad blockers often block Google Analytics. Consider using a proxy or server-side tracking for critical metrics
|
||||
|
||||
{% faq
|
||||
title="Frequently Asked Questions"
|
||||
items=[
|
||||
{"question": "Does MakerKit support Universal Analytics?", "answer": "No. Universal Analytics was sunset by Google in July 2023. MakerKit only supports Google Analytics 4 (GA4)."},
|
||||
{"question": "Can I use Google Tag Manager instead?", "answer": "Yes, but you would need to create a custom analytics provider. The built-in plugin uses gtag.js directly."},
|
||||
{"question": "How do I track conversions?", "answer": "Use analytics.trackEvent() with your conversion event name. Configure the event as a conversion in GA4 Admin > Events > Mark as conversion."},
|
||||
{"question": "Is server-side tracking supported?", "answer": "No. The Google Analytics plugin is client-side only. Use the Measurement Protocol API directly if you need server-side GA4 tracking."}
|
||||
]
|
||||
/%}
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Learn about Analytics and Events](analytics-and-events) for custom event tracking
|
||||
- [Add PostHog](posthog-analytics-provider) for product analytics
|
||||
- [Create a custom provider](custom-analytics-provider) for other services
|
||||
32
docs/analytics/meshes-provider.mdoc
Normal file
32
docs/analytics/meshes-provider.mdoc
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
status: "published"
|
||||
title: 'Using the Meshes Analytics Provider in Next.js Supabase Turbo'
|
||||
label: 'Meshes'
|
||||
description: 'Add Meshes to your MakerKit application for event tracking and analytics.'
|
||||
order: 6
|
||||
---
|
||||
|
||||
[Meshes](https://meshes.io/) is a platform for event tracking for user engagement and conversions. It captures custom events (signups, plan upgrades, feature usage) so you can measure what matters without building your own event pipeline.
|
||||
|
||||
## Installation
|
||||
|
||||
Install the Meshes plugin using the MakerKit CLI:
|
||||
|
||||
```bash
|
||||
npx @makerkit/cli@latest plugins add meshes-analytics
|
||||
```
|
||||
|
||||
The Makerkit CLI will automatically wire up the plugin in your project, so you don't have to do anything manually
|
||||
|
||||
The codemod is very complex, so please review the changes with `git diff` and commit them.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Set the analytics provider and Meshes configuration:
|
||||
|
||||
```bash title=".env.local"
|
||||
# Meshes configuration
|
||||
NEXT_PUBLIC_MESHES_PUBLISHABLE_KEY=your_api_key_here
|
||||
```
|
||||
|
||||
Please [read the Meshes documentation](https://meshes.io/docs) for more information on how to use the Meshes analytics provider.
|
||||
202
docs/analytics/posthog-analytics-provider.mdoc
Normal file
202
docs/analytics/posthog-analytics-provider.mdoc
Normal file
@@ -0,0 +1,202 @@
|
||||
---
|
||||
status: "published"
|
||||
title: 'Using the PostHog Analytics Provider in Next.js Supabase Turbo'
|
||||
label: 'PostHog'
|
||||
description: 'Add PostHog to your MakerKit application for product analytics, session replay, and feature flags with client-side and server-side support.'
|
||||
order: 3
|
||||
---
|
||||
|
||||
PostHog provides product analytics, session replay, feature flags, and A/B testing in one platform.
|
||||
|
||||
Unlike marketing-focused tools, PostHog helps you understand how users interact with your product.
|
||||
|
||||
Posthog supports both client-side and server-side tracking, and can be self-hosted for full data control.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before starting:
|
||||
|
||||
- Create a PostHog account at [posthog.com](https://posthog.com)
|
||||
- Note your Project API Key (starts with `phc_`)
|
||||
- Choose your region: `eu.posthog.com` or `us.posthog.com`
|
||||
|
||||
Find your API key in PostHog: **Project Settings > Project API Key**.
|
||||
|
||||
## Installation
|
||||
|
||||
Install the PostHog plugin using the MakerKit CLI:
|
||||
|
||||
```bash
|
||||
npx @makerkit/cli@latest plugins add posthog
|
||||
```
|
||||
|
||||
Our codemod will wire up the plugin in your project, so you don't have to do anything manually. Please review the changes with `git diff`.
|
||||
|
||||
Add environment variables:
|
||||
|
||||
```bash {% title=".env.local" %}
|
||||
NEXT_PUBLIC_POSTHOG_KEY=phc_your_key_here
|
||||
NEXT_PUBLIC_POSTHOG_HOST=https://eu.posthog.com
|
||||
```
|
||||
|
||||
Use `https://us.posthog.com` if your project is in the US region.
|
||||
|
||||
## Server-Side Configuration
|
||||
|
||||
PostHog supports server-side analytics for tracking events in API routes and Server Actions:
|
||||
|
||||
Use server-side tracking in your code:
|
||||
|
||||
```typescript
|
||||
import { analytics } from '@kit/analytics/server';
|
||||
|
||||
export async function createProject(data: ProjectData) {
|
||||
const project = await db.projects.create(data);
|
||||
|
||||
await analytics.trackEvent('project.created', {
|
||||
projectId: project.id,
|
||||
userId: data.userId,
|
||||
});
|
||||
|
||||
return project;
|
||||
}
|
||||
```
|
||||
|
||||
## Bypassing Ad Blockers with Ingestion Rewrites
|
||||
|
||||
Ad blockers frequently block PostHog. Use Next.js rewrites to proxy requests through your domain:
|
||||
|
||||
### Step 1: Add the Ingestion URL
|
||||
|
||||
```bash {% title=".env.local" %}
|
||||
NEXT_PUBLIC_POSTHOG_KEY=phc_your_key_here
|
||||
NEXT_PUBLIC_POSTHOG_HOST=https://eu.posthog.com
|
||||
NEXT_PUBLIC_POSTHOG_INGESTION_URL=http://localhost:3000/ingest
|
||||
```
|
||||
|
||||
In production, replace `localhost:3000` with your domain.
|
||||
|
||||
### Step 2: Configure Next.js Rewrites
|
||||
|
||||
Add rewrites to your Next.js configuration:
|
||||
|
||||
```javascript {% title="apps/web/next.config.mjs" %}
|
||||
/** @type {import('next').NextConfig} */
|
||||
const config = {
|
||||
// Required for PostHog trailing slash API requests
|
||||
skipTrailingSlashRedirect: true,
|
||||
|
||||
async rewrites() {
|
||||
// Change 'eu' to 'us' if using the US region
|
||||
return [
|
||||
{
|
||||
source: '/ingest/static/:path*',
|
||||
destination: 'https://eu-assets.i.posthog.com/static/:path*',
|
||||
},
|
||||
{
|
||||
source: '/ingest/:path*',
|
||||
destination: 'https://eu.i.posthog.com/:path*',
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
### Step 3: Exclude Ingestion Endpoint from Middleware
|
||||
|
||||
Ensure the ingestion endpoint is excluded from the middleware matcher:
|
||||
|
||||
```typescript {% title="apps/web/proxy.ts" %}
|
||||
export const config = {
|
||||
matcher: [
|
||||
'/((?!_next/static|_next/image|images|locales|assets|ingest/*|api/*).*)',
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `NEXT_PUBLIC_POSTHOG_KEY` | Yes | Your PostHog Project API Key |
|
||||
| `NEXT_PUBLIC_POSTHOG_HOST` | Yes | PostHog host (`https://eu.posthog.com` or `https://us.posthog.com`) |
|
||||
| `NEXT_PUBLIC_POSTHOG_INGESTION_URL` | No | Proxy URL to bypass ad blockers (e.g., `https://yourdomain.com/ingest`) |
|
||||
|
||||
## Verification
|
||||
|
||||
After configuration:
|
||||
|
||||
1. Open your application
|
||||
2. Navigate between pages
|
||||
3. Open PostHog > Activity > Live Events
|
||||
4. Confirm page views and events appear
|
||||
|
||||
If using ingestion rewrites, check the Network tab for requests to `/ingest` instead of `posthog.com`.
|
||||
|
||||
## Using with Other Providers
|
||||
|
||||
PostHog works alongside other analytics providers:
|
||||
|
||||
```typescript {% title="packages/analytics/src/index.ts" %}
|
||||
import { createPostHogAnalyticsService } from '@kit/posthog/client';
|
||||
import { createGoogleAnalyticsService } from '@kit/google-analytics';
|
||||
|
||||
import { createAnalyticsManager } from './analytics-manager';
|
||||
|
||||
export const analytics = createAnalyticsManager({
|
||||
providers: {
|
||||
posthog: createPostHogAnalyticsService,
|
||||
'google-analytics': createGoogleAnalyticsService,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## PostHog Features Beyond Analytics
|
||||
|
||||
PostHog offers additional features beyond event tracking:
|
||||
|
||||
- **Session Replay**: Watch user sessions to debug issues
|
||||
- **Feature Flags**: Control feature rollouts
|
||||
- **A/B Testing**: Run experiments on UI variants
|
||||
- **Surveys**: Collect user feedback
|
||||
|
||||
These features are available in the PostHog dashboard once you are capturing events.
|
||||
|
||||
For monitoring features (error tracking), see the [PostHog Monitoring guide](/docs/next-supabase-turbo/monitoring/posthog).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Events not appearing
|
||||
|
||||
- Verify your API key starts with `phc_`
|
||||
- Confirm the host matches your project region (EU vs US)
|
||||
- Check for ad blockers if not using ingestion rewrites
|
||||
|
||||
### CORS errors with ingestion rewrites
|
||||
|
||||
- Ensure `skipTrailingSlashRedirect: true` is set in next.config.mjs
|
||||
- Verify the rewrite destination matches your region
|
||||
|
||||
### Server-side events not appearing
|
||||
|
||||
- Ensure you import from `@kit/analytics/server`, not `@kit/analytics`
|
||||
- Server-side tracking requires the same environment variables
|
||||
|
||||
{% faq
|
||||
title="Frequently Asked Questions"
|
||||
items=[
|
||||
{"question": "Should I use the EU or US region?", "answer": "Use the EU region (eu.posthog.com) if you have European users and want GDPR-compliant data residency. The US region may have slightly lower latency for US-based users."},
|
||||
{"question": "Can I self-host PostHog?", "answer": "Yes. PostHog can be self-hosted using Docker. Update NEXT_PUBLIC_POSTHOG_HOST to your self-hosted instance URL."},
|
||||
{"question": "How do I enable session replay?", "answer": "Session replay is enabled by default in PostHog. Configure recording settings in PostHog > Project Settings > Session Replay."},
|
||||
{"question": "Do ingestion rewrites work on Vercel?", "answer": "Yes. The rewrites in next.config.mjs work on Vercel and other Next.js hosting platforms."},
|
||||
{"question": "Is PostHog GDPR compliant?", "answer": "PostHog can be GDPR compliant. Use the EU region for data residency, enable cookie-less tracking, and integrate with a consent management solution."}
|
||||
]
|
||||
/%}
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Learn about Analytics and Events](analytics-and-events) for custom event tracking
|
||||
- [Set up PostHog for monitoring](/docs/next-supabase-turbo/monitoring/posthog)
|
||||
- [Try Umami](umami-analytics-provider) for simpler, privacy-focused analytics
|
||||
151
docs/analytics/umami-analytics-provider.mdoc
Normal file
151
docs/analytics/umami-analytics-provider.mdoc
Normal file
@@ -0,0 +1,151 @@
|
||||
---
|
||||
status: "published"
|
||||
title: 'Using the Umami Analytics Provider in Next.js Supabase Turbo'
|
||||
label: 'Umami'
|
||||
description: 'Add Umami to your MakerKit application for privacy-focused, cookie-free analytics that comply with GDPR without consent banners.'
|
||||
order: 4
|
||||
---
|
||||
|
||||
Umami is a privacy-focused analytics platform that tracks page views and events without cookies.
|
||||
|
||||
Because it does not use cookies or collect personal data, you can use Umami without displaying cookie consent banners in the EU. Umami can be self-hosted for complete data ownership or used via Umami Cloud.
|
||||
|
||||
## Why Choose Umami
|
||||
|
||||
| Feature | Umami | Google Analytics |
|
||||
|---------|-------|------------------|
|
||||
| Cookies | None | Yes |
|
||||
| GDPR consent required | No | Yes |
|
||||
| Self-hosting | Yes | No |
|
||||
| Pricing | Free (self-hosted) or paid cloud | Free |
|
||||
| Data ownership | Full | Google |
|
||||
| Session replay | No | No |
|
||||
| Feature flags | No | No |
|
||||
|
||||
**Use Umami when**: You want simple, clean metrics without privacy concerns. Ideal for landing pages, documentation sites, and applications where marketing attribution is not critical.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before starting:
|
||||
|
||||
- Create an Umami account at [umami.is](https://umami.is) or self-host
|
||||
- Create a website in your Umami dashboard
|
||||
- Note your **Website ID** and **Script URL**
|
||||
|
||||
In Umami Cloud, find these at: **Settings > Websites > Your Website > Edit**.
|
||||
|
||||
## Installation
|
||||
|
||||
Install the Umami plugin using the MakerKit CLI:
|
||||
|
||||
```bash
|
||||
npx @makerkit/cli@latest plugins add umami
|
||||
```
|
||||
|
||||
Our codemod will wire up the plugin in your project, so you don't have to do anything manually. Please review the changes with `git diff`.
|
||||
|
||||
Add environment variables:
|
||||
|
||||
```bash {% title=".env.local" %}
|
||||
NEXT_PUBLIC_UMAMI_HOST=https://cloud.umami.is/script.js
|
||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID=your-website-id
|
||||
```
|
||||
|
||||
### Self-Hosted Configuration
|
||||
|
||||
If self-hosting Umami, point to your instance:
|
||||
|
||||
```bash {% title=".env.local" %}
|
||||
NEXT_PUBLIC_UMAMI_HOST=https://analytics.yourdomain.com/script.js
|
||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID=your-website-id
|
||||
```
|
||||
|
||||
Replace the URL with the path to your Umami instance's tracking script.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `NEXT_PUBLIC_UMAMI_HOST` | Yes | URL to the Umami tracking script |
|
||||
| `NEXT_PUBLIC_UMAMI_WEBSITE_ID` | Yes | Your website ID from Umami |
|
||||
| `NEXT_PUBLIC_UMAMI_DISABLE_LOCALHOST_TRACKING` | No | Set to `false` to enable localhost tracking |
|
||||
|
||||
### Development Configuration
|
||||
|
||||
By default, Umami does not track localhost. Enable it for development testing:
|
||||
|
||||
```bash {% title=".env.local" %}
|
||||
NEXT_PUBLIC_UMAMI_HOST=https://cloud.umami.is/script.js
|
||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID=your-website-id
|
||||
NEXT_PUBLIC_UMAMI_DISABLE_LOCALHOST_TRACKING=false
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
After configuration:
|
||||
|
||||
1. Deploy to a non-localhost environment (or enable localhost tracking)
|
||||
2. Open your application and navigate between pages
|
||||
3. Check your Umami dashboard > Realtime
|
||||
4. Confirm page views appear
|
||||
|
||||
## Custom Event Tracking
|
||||
|
||||
Umami tracks page views automatically. For custom events:
|
||||
|
||||
```typescript
|
||||
import { analytics } from '@kit/analytics';
|
||||
|
||||
void analytics.trackEvent('Button Clicked', {
|
||||
button: 'signup',
|
||||
location: 'header',
|
||||
});
|
||||
```
|
||||
|
||||
Events appear in Umami under **Events** in your website dashboard.
|
||||
|
||||
## Using with Other Providers
|
||||
|
||||
Umami can run alongside other providers:
|
||||
|
||||
```typescript {% title="packages/analytics/src/index.ts" %}
|
||||
import { createUmamiAnalyticsService } from '@kit/umami';
|
||||
import { createPostHogAnalyticsService } from '@kit/posthog/client';
|
||||
|
||||
import { createAnalyticsManager } from './analytics-manager';
|
||||
|
||||
export const analytics = createAnalyticsManager({
|
||||
providers: {
|
||||
umami: createUmamiAnalyticsService,
|
||||
posthog: createPostHogAnalyticsService,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
This setup provides Umami's clean metrics alongside PostHog's product analytics.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No data appearing
|
||||
|
||||
- Verify you are not on localhost (or enable localhost tracking)
|
||||
- Check that the Website ID matches your Umami dashboard
|
||||
- Confirm the script URL is correct and accessible
|
||||
|
||||
### Ad blocker interference
|
||||
|
||||
Umami is sometimes blocked by ad blockers. If this is an issue:
|
||||
|
||||
1. Self-host Umami on a subdomain (e.g., `analytics.yourdomain.com`)
|
||||
2. Use a generic script path (e.g., `/stats.js` instead of `/script.js`)
|
||||
|
||||
### Events not tracked
|
||||
|
||||
- Ensure event names and properties are strings
|
||||
- Check that the event appears in Umami's Events tab, not just Page Views
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Learn about Analytics and Events](analytics-and-events) for event tracking patterns
|
||||
- [Consider PostHog](posthog-analytics-provider) if you need user identification or feature flags
|
||||
- [Create a custom provider](custom-analytics-provider) for other analytics services
|
||||
Reference in New Issue
Block a user