Methodology. This tutorial synthesizes the Mixpanel JavaScript and Node SDK documentation as of May 2026. API shapes match mixpanel-browser v2.x and mixpanel (Node) v0.18+. Pricing references are taken from mixpanel.com/pricing and the published 100K MTU free tier. SDK ergonomics and the pricing table change over time — verify against the official docs before shipping.

Product analytics is the second platform decision (after auth and database) that solo SaaS founders defer for too long. The cost of waiting is a fog of unknowns: which signups never reach the activation moment, which features correlate with retention, which customers are about to churn. The cost of doing it badly is a noisy event stream that’s impossible to query usefully. The fix is to wire one tool early with a small, opinionated event schema, then build three saved dashboards on top of it.

This guide walks the canonical pattern for adding Mixpanel to a Next.js + App Router SaaS: the choose-Mixpanel-vs-PostHog decision, the SDK setup, identifying users on login, the five events every product should track, server-side tracking from Stripe webhooks, and the three dashboards that drive a weekly product review. The code is TypeScript on Next.js but the event model and dashboard playbook apply to any analytics stack.

1 Pick Mixpanel vs alternatives

Three serious options for a solo SaaS founder shipping product analytics in 2026:

Tool Strength Free tier Pricing past free Best for
Mixpanel Deepest cohort + funnel UI 100K MTU, 1B events/yr $0.04 per MTU above 100K Funnel-driven product teams
PostHog Bundled feature flags, session replay, error tracking 1M events/mo Per-event metering Broad telemetry, open-source bias
Amplitude Mature cohort analysis 10M events/mo Custom enterprise pricing Larger teams, enterprise procurement

The Mixpanel-vs-PostHog choice is the one most solo founders weigh seriously. Pick Mixpanel when funnel and cohort analysis is the primary need — the UI for “how many users hit step 1, then step 2, then step 3, broken down by signup source” is best-in-class, and the Insights and Funnels reports are genuinely faster to compose than the equivalents in PostHog.

Pick PostHog when you want analytics plus session recording plus feature flags plus error tracking in one bundle. The trade-off is breadth-vs-depth: PostHog covers more surface but the funnels and cohort UI is a step behind Mixpanel’s. The PostHog review and Plausible vs PostHog vs Fathom breakdowns drill into the trade in detail.

Amplitude is the third name worth mentioning; for a solo founder its main advantage is the larger free tier, but the UI is heavier and the upgrade path is enterprise-priced. Skip it unless you have a specific reason.

2 Create a Mixpanel project

Sign up at mixpanel.com, create a project, and copy the project token from Project Settings. The token is the API key the browser and Node SDKs use to authenticate event writes; it’s not secret in the strict sense (it ships in browser bundles) but it’s scoped to your project.

The free tier as of May 2026: 100,000 monthly tracked users (MTUs) and 1 billion events per year. An MTU is any anonymous or identified user who fires at least one tracked event in a calendar month. For most solo SaaS founders, 100K MTU comfortably covers the first 12 to 24 months of growth.

Two project settings to choose at creation:

  • Region. US (api.mixpanel.com) is the default; EU (api-eu.mixpanel.com) is the right pick if your customers are in the EU or you need to keep event data inside the EU for GDPR reasons. Region cannot be changed later without recreating the project.
  • Timezone. Set this to the timezone your team operates in. All Mixpanel reports bucket by day in the project timezone, so getting it right at creation makes daily-active-user counts intuitive.

Persist the token in your environment as NEXT_PUBLIC_MIXPANEL_TOKEN (the NEXT_PUBLIC_ prefix is needed because the browser SDK reads it directly). For the Node SDK, the same token works.

3 Install the SDKs

Two packages for full coverage: mixpanel-browser for client-side events and mixpanel (the Node SDK) for server-side tracking from API routes, server actions, and webhooks.

npm install mixpanel-browser mixpanel
npm install --save-dev @types/mixpanel-browser

# .env.local
NEXT_PUBLIC_MIXPANEL_TOKEN=your-project-token-here

The two SDKs share the same event format (an event name plus a JSON properties object), which means events fired from either side land in the same project and merge correctly on the user ID. The split exists because the browser SDK ships with autocapture, session replay opt-ins, and a queue/retry layer tuned for unreliable client networks, while the Node SDK is a thin HTTP wrapper optimised for server reliability.

4 Wrap the App Router in a Mixpanel provider

In App Router, Mixpanel initialisation must run on the client. The canonical pattern is a small client-side providers.tsx that initialises Mixpanel once at app boot and exposes a typed wrapper through context.

// app/providers.tsx
'use client';

import { useEffect } from 'react';
import mixpanel from 'mixpanel-browser';

const TOKEN = process.env.NEXT_PUBLIC_MIXPANEL_TOKEN;

let initialized = false;

function ensureInitialized() {
  if (initialized || !TOKEN) return;
  mixpanel.init(TOKEN, {
    debug: process.env.NODE_ENV === 'development',
    track_pageview: true,
    persistence: 'localStorage',
    api_host: 'https://api.mixpanel.com', // use api-eu for EU residency
    ignore_dnt: false,
    record_sessions_percent: 0
  });
  initialized = true;
}

export function MixpanelProvider({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    ensureInitialized();
  }, []);

  return <>{children}</>;
}

Wire it into the root layout:

// app/layout.tsx
import { MixpanelProvider } from './providers';

export default function RootLayout({
  children
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <MixpanelProvider>
          {children}
        </MixpanelProvider>
      </body>
    </html>
  );
}

Four configuration choices encoded here. track_pageview: true turns on Mixpanel’s built-in pageview event for every route change — the cheapest piece of analytics you’ll ever wire and the foundation of the activation funnel. persistence: 'localStorage' keeps the user’s anonymous ID stable across browser tabs and sessions, which is critical for the alias step in Step 5. ignore_dnt: false respects the “Do Not Track” browser header, which is the right default for any SaaS that ships outside the US. And record_sessions_percent: 0 leaves Mixpanel’s optional session-replay feature off — turn it on later only if you specifically need it.

5 Identify users on login

Mixpanel tracks events under an anonymous distinct ID until you call identify. The identify call ties the anonymous activity stream to a permanent user ID; the corresponding people.set call writes profile attributes that show up in the user profile view.

// lib/analytics.ts
import mixpanel from 'mixpanel-browser';

export type UserProfile = {
  id: string;
  email: string;
  name?: string;
  plan?: 'free' | 'pro' | 'enterprise';
  signupAt?: string;
  organizationId?: string;
};

export function identifyUser(user: UserProfile) {
  // Tie the anonymous distinct_id to the permanent user ID.
  mixpanel.identify(user.id);

  // Set profile attributes (last value wins).
  mixpanel.people.set({
    $email: user.email,
    $name: user.name ?? user.email,
    plan: user.plan ?? 'free',
    signup_at: user.signupAt,
    organization_id: user.organizationId
  });

  // For B2B: associate the user with their organization for group analytics.
  if (user.organizationId) {
    mixpanel.set_group('organization_id', user.organizationId);
  }
}

export function resetUser() {
  // Call on logout so subsequent events are anonymous again.
  mixpanel.reset();
}

Call identifyUser from your auth callback right after a successful sign-in, and call resetUser on logout. The $email and $name properties (with the dollar prefix) are Mixpanel’s reserved profile fields and render with extra polish in the user profile UI.

The most important call here is the implicit one: the first time a brand-new user signs up, Mixpanel uses alias under the hood to merge the anonymous pre-signup activity stream onto the new permanent ID. Without this merge, the signup itself shows up as the user’s “first event” and you lose the pre-signup funnel data. The browser SDK handles this automatically when identify is called on a previously-anonymous browser; the gotcha is calling identify too early (before signup completes) or too late (after the anonymous events have rolled off retention).

6 Track the five canonical SaaS events

The temptation in a fresh analytics setup is to track everything — every button click, every form submit, every page hover. Resist it. The first five events to wire are universal across SaaS:

  • signup — fired when an account is created. Carries the signup source (referrer, UTM parameters).
  • activation — fired the first time the user completes the “aha” action that signals the product has clicked. Define this explicitly per product (created a first project, sent the first invoice, generated the first AI completion).
  • subscription_started — fired when a paid plan begins. Includes the plan name and MRR.
  • subscription_cancelled — fired when a paid plan ends. Carries the cancellation reason if you collect it.
  • primary_value_event — the single event that best captures the user getting value. For an email-marketing tool, sending a campaign. For an analytics tool, viewing a saved dashboard. Pick one; this becomes the denominator for retention curves.

Centralise the calls behind a typed helper so every event is fired the same way:

// lib/track.ts
import mixpanel from 'mixpanel-browser';

type EventMap = {
  signup: {
    method: 'email' | 'google' | 'github';
    source?: string;
    utm_source?: string;
    utm_medium?: string;
    utm_campaign?: string;
  };
  activation: {
    activation_event: string;
    time_to_activate_seconds: number;
  };
  subscription_started: {
    plan: string;
    mrr_cents: number;
    billing_period: 'monthly' | 'annual';
  };
  subscription_cancelled: {
    plan: string;
    reason?: string;
    days_since_signup: number;
  };
  primary_value_event: {
    feature: string;
    value_amount?: number;
  };
};

export function track<K extends keyof EventMap>(
  event: K,
  properties: EventMap[K]
) {
  mixpanel.track(event, properties);
}

Typed event names plus typed property shapes prevent the most common analytics bug: drift between “sub_started” in one component and “subscription_started” in another. Mixpanel will happily accept both spellings and create two separate event series, leaving you with funnels that miss half their data.

Beyond these five, add events deliberately as questions come up. The discipline is “every event needs a question it answers,” not “every interaction needs an event.” The complete-guide breakdown in our guide to SaaS analytics covers the longer event list once these five are in place.

7 Server-side tracking from webhooks

The five events above all fire from the client. Three of them — signup, subscription_started, subscription_cancelled — are better fired from the server, because the source of truth is the database (or Stripe) and the client doesn’t always see the final state. A user who closes the browser tab during the Stripe redirect never fires subscription_started from the client; the Stripe webhook fires reliably no matter what.

// lib/server-analytics.ts
import Mixpanel from 'mixpanel';

const mp = Mixpanel.init(process.env.MIXPANEL_TOKEN!, {
  host: 'api.mixpanel.com' // use api-eu for EU residency
});

type ServerEvent =
  | { name: 'subscription_started'; userId: string; orgId?: string; plan: string; mrrCents: number }
  | { name: 'subscription_cancelled'; userId: string; orgId?: string; plan: string; reason?: string }
  | { name: 'signup'; userId: string; method: string }
  | { name: 'webhook_received'; userId: string; webhook: string };

export function trackServer(event: ServerEvent) {
  const { name, userId, ...rest } = event;
  mp.track(name, {
    distinct_id: userId,
    ...rest,
    source: 'server'
  });
}

Call trackServer from the Stripe webhook handler:

// app/api/webhooks/stripe/route.ts (excerpt)
import { stripe } from '@/lib/stripe';
import { trackServer } from '@/lib/server-analytics';
import { lookupUserByCustomer } from '@/lib/billing';

async function onCheckoutCompleted(session: any) {
  const userId = await lookupUserByCustomer(session.customer);
  if (!userId) return;

  trackServer({
    name: 'subscription_started',
    userId,
    plan: session.metadata?.plan ?? 'unknown',
    mrrCents: session.amount_total ?? 0
  });
}

The distinct_id property is the linchpin: it matches the user ID used in identify on the client, so events from both sides merge into the same user profile. Pass the Mixpanel token as a non-public environment variable on the server (skip the NEXT_PUBLIC_ prefix); the Node SDK never ships to the browser, so the token stays server-only when used here.

Use server-side tracking for any event the client cannot observe reliably: Stripe webhook events, scheduled jobs, background processing completions, queue-driven workflows, anything fired from a cron task or a downstream service.

8 The three dashboards every SaaS needs

Tracking events is the easy part; turning them into insight is the discipline. Three saved dashboards are the bedrock of a weekly product review.

1. Activation funnel

A Mixpanel Funnel report from signupactivationsubscription_started, segmented by signup source. This dashboard answers “which acquisition channels turn into paying customers?” The number you watch weekly is the signup-to-activation conversion rate — if it drops, something in onboarding regressed.

Build it: New Insights → Funnels → add steps in order → group by $initial_referring_domain or your utm_source property. Save with the name “Activation funnel.”

2. Retention curve

A Mixpanel Retention report on primary_value_event from signup, bucketed by week. This dashboard answers “do users keep coming back?” The shape that matters is the curve flattening — if week 4 retention is similar to week 1 retention, you have real product-market fit; if the curve hits zero by week 3, you have an activation or value-delivery problem.

Build it: New Insights → Retention → first event signup → return event primary_value_event → bucket by week. Save with the name “Retention.”

3. MRR breakdown

A Mixpanel Insights report summing subscription_started.mrr_cents minus subscription_cancelled.mrr_cents, broken down by plan. This dashboard answers “is the business growing this week?” The mainline number to watch is net new MRR; the secondary number is the cancellation reason breakdown (if you collect it on the cancel flow).

Build it: New Insights → Insights → show total of subscription_started.mrr_cents minus subscription_cancelled.mrr_cents → group by plan → weekly cadence. Save with the name “MRR.”

Once these three are saved, configure each as a Mixpanel-emailed weekly digest delivered to your inbox every Monday morning. That digest, paired with the open Mixpanel app for a 20-minute deep-dive, is the entire weekly product review most solo SaaS founders need.

Gotchas and operational notes

Group analytics for B2B

If your SaaS sells to companies and not individuals, the natural unit of analysis is the organization, not the user. Mixpanel’s group analytics feature handles this through mixpanel.set_group('organization_id', orgId) on the client and the group_key parameter on the server SDK. Once configured, every event carries an organization ID and Mixpanel can build funnels and retention curves on the organization grain (“what percentage of organizations reach activation?”) rather than the user grain. Enable it in Project Settings → Group Analytics and add the group property to the identify call from Step 5.

The anonymous-to-identified merge

The single most common analytics-data bug is forgetting to call mixpanel.alias on signup. Without it, the pre-signup anonymous distinct_id and the post-signup permanent user_id are treated as two different users, and your activation funnel splits in half — half the “signup” events have no preceding page view, half the page views have no following signup. The browser SDK’s identify call handles this automatically when called for the first time on a previously-anonymous user, but the canonical pattern is to call mixpanel.alias(newUserId) exactly once at the moment of signup, and mixpanel.identify(userId) on every subsequent login. Skip the alias and your activation data is permanently fragmented.

EU data residency

Mixpanel offers two regional ingestion endpoints: api.mixpanel.com (US) and api-eu.mixpanel.com (EU). Pick EU at project creation if your customers are EU residents or if your privacy policy commits to EU data residency. The region is set per project and cannot be changed later without recreating the project and re-instrumenting, so make the call deliberately. Pass api_host: 'https://api-eu.mixpanel.com' in the browser SDK init and host: 'api-eu.mixpanel.com' in the Node SDK init.

Don’t track PII in event properties

Email is fine as a profile attribute ($email on the user profile). Plaintext credit card numbers, full SSNs, government IDs, health records, or other sensitive personal data should never appear in event properties — you don’t want them in an analytics database, you don’t want them in Mixpanel’s exports, and you don’t want them in the data warehouse you eventually pipe Mixpanel data into. The rule: if it would be a problem in a CSV export, don’t track it.

Sampling: disable for revenue events, allow for noisy ones

At scale, Mixpanel can sample events to keep ingest costs predictable. Sampling is appropriate for high-volume noisy events (every keystroke in a search box, every cursor hover) where the aggregated count is what matters and an exact event-by-event ledger is not. Sampling is never appropriate for revenue events (subscription_started, subscription_cancelled) or rare conversion events — the right number of samples for a $99 MRR signup is one, and losing it is losing the data point entirely. Disable sampling explicitly on these events.

Cost surprise above 100K MTU

The free tier ends at 100,000 MTUs. Past that, Mixpanel meters at roughly $0.04 per additional MTU on the entry-level paid plan, which sounds small but adds up: 10,000 extra MTUs is $400 a month. Founders are most often surprised by two MTU-multiplier patterns: a marketing-driven traffic spike that adds anonymous MTUs (every visitor who fires a pageview counts), and a B2B SaaS where the natural unit is organizations but the meter counts the users inside those organizations. Watch the MTU graph weekly and turn off Mixpanel on logged-out marketing pages if anonymous MTUs are inflating the bill without adding value. The best SaaS tools for developers roundup compares Mixpanel’s cost curve against PostHog’s and the rest of the analytics market.

Server-side reliability

The Node SDK’s track call is fire-and-forget by default: it doesn’t wait for the HTTP request to complete before returning. In a long-running server this is fine; in a serverless function (Vercel, Lambda) the function can return and the request gets cancelled before the event reaches Mixpanel. Use the Node SDK’s import-batch or the await-friendly methods, or buffer events to a queue (Redis, SQS) processed by a worker, to avoid event loss in serverless environments.

Summary
pick the tool → init → identify → 5 events → server-side → 3 dashboards

Mixpanel is the cleanest pick for solo SaaS founders whose primary analytics need is funnel and cohort analysis. The 100K MTU free tier covers the first 12 to 24 months of growth; past that, $0.04 per MTU is meaningful and predictable. The canonical setup is a client-side provider plus a Node SDK on the server, a typed track helper around the five canonical events (signup, activation, subscription_started, subscription_cancelled, primary_value_event), server-side tracking from Stripe webhooks, and three saved dashboards — activation funnel, retention curve, MRR breakdown — delivered as a Monday-morning emailed digest.

Related guides

Get one SaaS build breakdown every week

The stack, prompts, pricing, and mistakes to avoid — for solo founders building with AI.