# Server-Side Analytics

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

*Canonical: https://makerkit.dev/docs/nextjs-prisma/analytics/server-side-analytics*

---

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.

{% sequence title="Server-Side Analytics" description="Track events from your backend code" %}

[Why Server-Side Analytics](#why-server-side-analytics)

[Using Server Analytics](#using-server-analytics)

[Implementing a Server Provider](#implementing-a-server-provider)

{% /sequence %}

## 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`:

```typescript {% title="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:

```typescript
// 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`:

```typescript {% title="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:

```typescript {% title="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:

```typescript {% title="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

```typescript {% title="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

```typescript {% title="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:

```typescript
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.

{% faq
   title="Frequently Asked Questions"
   items=[
     {"question": "Can I use the same provider for client and server?", "answer": "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."},
     {"question": "Should I track all events server-side?", "answer": "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."},
     {"question": "How do I associate server events with users?", "answer": "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."},
     {"question": "Will server-side events appear in my analytics dashboard?", "answer": "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."}
   ]
/%}
