Server-Side Analytics

Learn how to track analytics events from API routes, server actions, and other server-side code in your Makerkit application.

Makerkit provides a separate analytics export for server-side tracking. This allows you to track events from API routes, server actions, and other backend code where the client-side analytics SDK isn't available.

Server-Side Analytics

Track events from your backend code

Why Server-Side Analytics

Server-side analytics is useful when:

  • Tracking sensitive events - Some events shouldn't be exposed to the client (revenue, internal metrics)
  • Ensuring reliability - Server events can't be blocked by ad blockers
  • Backend operations - Track events from webhooks, cron jobs, or background tasks
  • Data integrity - Server-side tracking is harder to spoof

Using Server Analytics

Import from @kit/analytics/server instead of @kit/analytics:

app/api/webhook/route.ts

import { analytics } from '@kit/analytics/server';
export async function POST(request: Request) {
const data = await request.json();
// Track the webhook event
await analytics.trackEvent('webhook.received', {
type: data.type,
source: 'stripe',
});
// Process the webhook...
return Response.json({ received: true });
}

The API is identical to client-side analytics:

// Identify a user
await analytics.identify(userId, { plan: 'pro' });
// Track an event
await analytics.trackEvent('subscription.created', {
plan: 'pro',
amount: '29',
});
// Track a page view (less common server-side)
await analytics.trackPageView('/api/data');

Default Server Configuration

By default, server analytics uses the NullAnalyticsService:

packages/analytics/src/server.ts

import 'server-only';
import { createAnalyticsManager } from './analytics-manager';
import { NullAnalyticsService } from './null-analytics-service';
import type { AnalyticsManager } from './types';
export const analytics: AnalyticsManager = createAnalyticsManager({
providers: {
null: () => NullAnalyticsService,
},
});

This means server-side tracking is disabled until you implement a provider that supports server-side calls.

Implementing a Server Provider

Not all analytics services support server-side tracking. For those that do (like PostHog, Segment, or Mixpanel), create a server-specific provider:

packages/analytics/src/providers/posthog-server-service.ts

import { PostHog } from 'posthog-node';
import type { AnalyticsService } from '../types';
export class PostHogServerService implements AnalyticsService {
private client: PostHog;
constructor() {
const apiKey = process.env.POSTHOG_API_KEY;
if (!apiKey) {
throw new Error('POSTHOG_API_KEY is not defined');
}
this.client = new PostHog(apiKey, {
host: process.env.POSTHOG_HOST || 'https://app.posthog.com',
});
}
async initialize(): Promise<void> {
// PostHog Node client initializes in constructor
}
async identify(
userId: string,
traits?: Record<string, string>
): Promise<void> {
this.client.identify({
distinctId: userId,
properties: traits,
});
}
async trackPageView(path: string): Promise<void> {
// Page views are typically client-side only
// You can implement if needed
}
async trackEvent(
eventName: string,
eventProperties?: Record<string, string | string[]>
): Promise<void> {
this.client.capture({
distinctId: 'server',
event: eventName,
properties: eventProperties,
});
}
}

Register it in the server analytics configuration:

packages/analytics/src/server.ts

import 'server-only';
import { createAnalyticsManager } from './analytics-manager';
import { PostHogServerService } from './providers/posthog-server-service';
import type { AnalyticsManager } from './types';
export const analytics: AnalyticsManager = createAnalyticsManager({
providers: {
posthog: () => new PostHogServerService(),
},
});

Example: Tracking in Server Actions

lib/actions/create-project.ts

'use server';
import { analytics } from '@kit/analytics/server';
export async function createProject(formData: FormData) {
const name = formData.get('name') as string;
const userId = await getCurrentUserId();
// Create the project...
const project = await db.project.create({
data: { name, userId },
});
// Track the event server-side
await analytics.trackEvent('project.created', {
projectId: project.id,
userId,
});
return project;
}

Example: Tracking Webhook Events

app/api/stripe/webhook/route.ts

'use server';
import { analytics } from '@kit/analytics/server';
export async function POST(request: Request) {
const event = await parseStripeWebhook(request);
switch (event.type) {
case 'checkout.session.completed':
await analytics.trackEvent('payment.completed', {
amount: String(event.data.object.amount_total),
currency: event.data.object.currency,
customerId: event.data.object.customer as string,
});
break;
case 'customer.subscription.deleted':
await analytics.trackEvent('subscription.cancelled', {
customerId: event.data.object.customer as string,
reason: event.data.object.cancellation_details?.reason || 'unknown',
});
break;
}
return Response.json({ received: true });
}

Server-Only Import Guard

The server analytics module uses the 'server-only' directive:

import 'server-only';

This ensures the module can only be imported in server-side code. If you accidentally import it in a client component, Next.js will throw a build error.

Frequently Asked Questions

Can I use the same provider for client and server?
It depends on the provider. Some like PostHog have separate client and server SDKs. Others like Google Analytics are client-only. Check your provider's documentation for server-side support.
Should I track all events server-side?
No. Use server-side tracking for sensitive events, webhooks, and backend operations. Client-side tracking is better for user interactions, page views, and UI events where you need real-time browser context.
How do I associate server events with users?
Pass the userId to trackEvent as a property, or call identify() before tracking events. Most analytics services can link server and client events by user ID.
Will server-side events appear in my analytics dashboard?
Yes, if your provider supports server-side tracking. The events appear alongside client-side events in your dashboard, though they may be marked differently depending on the provider.