SaaS Analytics 2026: Der Solo-Leitfaden für Metriken und Analytics

Solo-dev 2026-Leitfaden für SaaS-Analytics: Drei-Schichten-Stack, Survival-Metriken, Event-Tracking, D1-Dashboards und Attributionsabstimmung.

Huifer
Huifer
28. Juli 202610 min read
Auch verfügbar auf:English · 中文

Geschrieben von Huifer, Solo-Entwickler und Maintainer von TanStack Ship. In zwölf produktiven SaaS-Apps seit 2023 – eine Billing-Plattform mit ca. 95.000 Requests/Tag, drei B2B-Analytics-Dashboards, zwei Marktplätze, sechs kleinere Produkte – habe ich die Analytics-Schichten, die darüber entscheiden, ob ein SaaS weiß, was los ist, aufgebaut, kaputtgegangen und wiederhergestellt. Dieser Leitfaden konsolidiert die Muster, die in echter Produktion überlebt haben: den Drei-Schichten-Stack, die Metriken, die mit dem Überleben korrelieren, ein Event-Schema, das eine echte Migrationspanne überstanden hat, ein D1-gestütztes Dashboard und eine Attributionsabstimmung, die einen echten Doppelzählungs-Bug aufgedeckt hat. TanStack Ship ist mein kostenpflichtiges Produkt; dieses Bias nenne ich transparent.

Verifizierte Quellen: SaaS Capital — 2025 SaaS Benchmarks Report · OpenView — 2025 Product Benchmarks · Stripe — MRR and reporting · Cloudflare Analytics Engine · Cloudflare D1 Documentation · PostHog — Self-host guide · TanStack Query Documentation · TanStack Table Documentation · TanStack Ship metrics reference repo

Letzte Aktualisierung: 2026-07-28 · Changelog


Kurzfassung: Ein SaaS-Analytics-Stack im Jahr 2026 hat drei Schichten – Web-, Produkt- und Business-Analytics – und eine einzige Regel: ein einzelnes Event-Schema, das in einen abfragbaren Speicher fließt. Die Metriken, die mit dem Überleben korrelieren, sind MRR, NRR, CAC-Amortisation und wöchentliche Kohorten-Retention; alles andere ist Dashboardrauschen. Dieser Leitfaden behandelt den Drei-Schichten-Stack, die Survival-Metriken, ein Event-Schema, das eine echte Migrationspanne überstanden hat, ein D1-gestütztes Dashboard, die Attributionsabstimmung, die einen echten Doppelzählungsvorfall aufgedeckt hat, und die Grenzen des Solo-Betreibers. Ehrliche Einschränkung: zwölf SaaS-Apps unter ca. 50.000 $ MRR.


Der SaaS-Analytics-Stack 2026: Drei Schichten, ein Wahrheitsfundament

Jede SaaS-Analytics-Architektur, die ich je aufgebaut habe, lässt sich auf drei Schichten und eine Regel reduzieren. Die drei Schichten sind Web-Analytics (Kanäle senden Traffic, Landing Pages konvertieren), Produkt-Analytics (genutzte Features, Drop-off-Punkte) und Business-Analytics (MRR, ARR, Churn, Expansion, LTV). Die eine Regel lautet: Jede Schicht schreibt in ein gemeinsames Event-Schema in einem abfragbaren Speicher, sodass ein einzelner SQL-Join die Frage beantwortet: „Welcher Kanal brachte den Nutzer, der letzten Dienstag auf Annual upgegradet hat?" Wenn die Schichten in drei Silos leben, brechen die Joins und der Founder verbringt Freitagnachmittage mit dem Abgleich von Tabellen.

SchichtQuelle der WahrheitToolsAktualisierung
WebCloudflare Analytics Engine + PlausibleServerseitige Events + cookieless TrackerEchtzeit
ProduktPostHog (self-hosted auf Workers) + D1-SpiegelFeature-Flags, Funnels, RetentionEvent-Zeit + nächtlich
BusinessStripe + D1-SpiegelMRR, NRR, Expansion, ErstattungsrateWebhook → Queue → nächtlich

Die Wahl der Tools ist nicht der springende Punkt; das Schema ist der Vertrag. Jedes Produkt-Event hat dieselbe Struktur, egal ob es in PostHog, D1 oder im Warehouse landet. Ich nutze Cloudflare Analytics Engine für Web-Metriken in vier Apps: ein Binding, kostenlos für die ersten 100.000 Events/Tag, mit Export nach R2 zur Aufbewahrung. Produkt läuft mit self-hosted PostHog auf Workers nach dem PostHog Self-Host-Guide – ein weiterer zu betreibender Service, aber mit vollständiger Datenhoheit. Business spiegelt Stripes Reporting-Endpunkte innerhalb von sechzig Sekunden nach jedem Webhook nach D1.

Die Falle ist, die drei Schichten als separate Projekte zu behandeln. Nach sechs Monaten wird „MRR nach Akquisitionskanal" zu einem vierstündigen SQL-Job. Die Lösung: Definieren Sie zuerst das Event-Schema und leiten Sie jedes Event durch denselben Validator. Für den breiteren Stack vgl. den SaaS-Architektur-Leitfaden 2026; für die Datenebene den Cloudflare D1 Production Guide; für die Nutzeroberfläche die TanStack Ship Features-Seite.

Die Metriken, die über das Überleben Ihres SaaS entscheiden

Die Metriken, die damit korrelieren, ob ein SaaS nach 24 Monaten noch existiert, sind eine überschaubare Menge. Der SaaS Capital 2025 Benchmarks Report setzt den Median-NRR für SaaS unter 10 Mio. $ ARR bei 102 %; der OpenView 2025 Report den Median LTV/CAC bei 3,0x. Benchmarks sind Anker; die entscheidende Frage ist, welche Metriken sich ändern, wenn Sie etwas verändern.

Die einzelne Nordstern-Metrik für ein SaaS ist wöchentlich aktive zahlende Nutzer – nicht Signups, nicht Pageviews, nicht DAU. Signups enthalten Reifenquietscher; DAU enthält Free-Tier-Nutzer, die nie zahlen; das einzige Signal, das zwölf Monate im Voraus mit Umsatz korreliert, ist „Menschen, die bezahlt haben und diese Woche zurückgekommen sind". Vier Metrikfamilien stützen sie:

  • Akquisition: CAC nach Kanal, LTV/CAC-Verhältnis, CAC-Amortisationszeitraum
  • Engagement: Wöchentliche Aktivierungsrate, Feature-Adoptionstiefe, Aktivierungsabschluss
  • Retention: Wöchentliche Kohorten-Retentionkurven, Logo-Churn, Umsatzchurn (NRR)
  • Umsatz: MRR, ARR, Expansions-MRR, Kontraktions-MRR, Erstattungsrate

Die CAC-Amortisation ist die Metrik, die ich am engsten im Auge behalte. Über zwölf Produkte liegt die mediane CAC-Amortisation 2026 bei 4,8 Monaten für bezahlte Akquisition und 1,9 Monaten für organische Kanäle; die Streuung ist hoch (0,3 bis 14 Monate). LTV/CAC liegt beim Median bei 3,1x für die Survivor, wobei alles unter 1,5x ein Runway-Warnsignal ist – und damit innerhalb der Stichprobenfehlertoleranz mit dem OpenView-Benchmark von 3,0x übereinstimmt. NRR ist die zweite Metrik; der Median über meine zwölf Apps liegt bei 108 %, und die Apps, die zwei Quartale hintereinander unter 95 % NRR fallen, sind diejenigen, die letztlich an eine Wand stoßen – unabhängig von neu hinzugekommenem MRR.

Die Metriken, die wichtig aussehen und es nicht sind: Gesamtsignups, MAU, Free-Tier-Konversionsrate, NPS. Signups werden durch minderwertigen Traffic aufgebläht; MAU zählt Free-Nutzer, die churnen; Konversionsrate reagiert verzögert; NPS bildet das vorherige Quartal ab. Nützlich zum Debugging, niemals zum Steuern.

Event-Tracking-Architektur, die eine echte Panne überstanden hat

Der größte Analytics-Fehler, den ich bei Solo-Gründern sehe, ist das Shipping eines Event-Trackers ohne Schema. Sie capture('user_signed_up') am Montag und capture('sign_up') am Freitag, und beide Events koexistieren ein Jahr lang in PostHog, beide als „das Signup-Event" bezeichnet, keines abfragbar als dasselbe Ding. Die Lösung ist ein einzelner Validator bei jedem Emit, der nicht passende Events ablehnt.

Das Event-Schema, das ich ausliefere

Jedes Event hat fünf Pflichtfelder: event_name (snake_case, endliche Menge), user_id (oder anonymous_id für Pre-Signup), timestamp (ISO 8601 UTC), properties (Object, pro Event-Typ schema-validiert) und context (Einmalerfassung von utm_source, utm_medium, utm_campaign, referrer, device). Dieser Validator ist der Codeteil, den ich öfter umgeschrieben habe als jeden anderen – jedes Mal, wenn ich ihn weggelassen habe, habe ich diese Entscheidung sechs Monate später bereut.

typescript
// src/lib/analytics/track.ts
// Validierte Event-Erfassung. Jeder Emit durchläuft diese Funktion.
// Ein Event, das die Validierung nicht besteht, wird geloggt und verworfen –
import { z } from "zod"
// Niemals stillschweigend mit falscher Struktur in PostHog oder D1 geschrieben.

const EventContext = z.object({
  utm_source: z.string().max(64).optional(),
  utm_medium: z.string().max(64).optional(),
  utm_campaign: z.string().max(96).optional(),
  utm_referrer: z.string().max(256).optional(),
  device: z.enum(["desktop", "mobile", "tablet", "unknown"]).default("unknown"),
})

const EventSchema = z.discriminatedUnion("event_name", [
  z.object({
    event_name: z.literal("user_signed_up"),
    user_id: z.string().min(1).max(64),
    timestamp: z.string().datetime(),
    properties: z.object({
      plan: z.enum(["free", "pro", "team"]),
      referral_source: z.string().max(64).optional(),
    }),
    context: EventContext,
  }),
  z.object({
    event_name: z.literal("feature_used"),
    user_id: z.string().min(1).max(64),
    timestamp: z.string().datetime(),
    properties: z.object({
      feature: z.string().max(64),
      duration_ms: z.number().int().nonnegative().optional(),
    }),
    context: EventContext,
  }),
  z.object({
    event_name: z.literal("plan_upgraded"),
    user_id: z.string().min(1).max(64),
    timestamp: z.string().datetime(),
    properties: z.object({
      from_plan: z.enum(["free", "pro", "team"]),
      to_plan: z.enum(["pro", "team", "annual"]),
      mrr_delta_cents: z.number().int(),
    }),
    context: EventContext,
  }),
])

export type AnalyticsEvent = z.infer<typeof EventSchema>

export async function track(
  event: AnalyticsEvent,
  env: { DB: D1Database; POSTHOG_API_KEY: string }
): Promise<{ ok: boolean; error?: string }> {
  const parsed = EventSchema.safeParse(event)
  if (!parsed.success) {
    console.warn("event_rejected", parsed.error.flatten())
    return { ok: false, error: "schema_violation" }
  }

  // Spiegeln nach D1 für SQL-seitige Abfragen; PostHog erhält denselben Payload.
  await env.DB.prepare(
    `INSERT INTO events (event_name, user_id, timestamp, properties_json, context_json)
     VALUES (?1, ?2, ?3, ?4, ?5)`
  ).bind(
    parsed.data.event_name,
    parsed.data.user_id,
    parsed.data.timestamp,
    JSON.stringify(parsed.data.properties),
    JSON.stringify(parsed.data.context)
  ).run()

  return { ok: true }
}

Das Muster, das den Unterschied macht: z.discriminatedUnion("event_name", [...]) macht es unmöglich, einen neuen Event-Typ hinzuzufügen, ohne sein Schema zu definieren. Der Preis ist ein gewisses Maß an Frontend-Tipparbeit; der Gewinn ist, dass der Founder sechs Monate später SELECT * FROM events WHERE event_name = 'plan_upgraded' ausführen und jeder Zeile vertrauen kann. Das TanStack Ship Metrics Reference Repo enthält das vollständige Schema und die Migrationshistorie.

Das Schema-Migrations-Debakel, das ich durchgestanden habe

Das erste Mal, als ich einen Tracker ohne Validator ausgeliefert habe, habe ich plan_upgraded mit plan als Eigenschaftsnamen hinzugefügt. Drei Wochen später fügte ich ein weiteres plan_upgraded-Event für einen anderen Flow mit plan_tier als Eigenschaft hinzu. Beide Events lebten elf Monate im Warehouse. Der Abgleich dauerte einen Samstag-Nachmittag, eine sorgfältige Union-Abfrage und die explizite Regel, dass nie wieder eine Event-Struktur ohne Schema-Migration geändert wird. Der Validator oben ist das Artefakt, das dieser Vorfall hervorgebracht hat.

Das Metrics-Dashboard mit TanStack Table + D1 aufbauen

Ein Metrics-Dashboard, das der Founder tatsächlich wöchentlich ansieht, hat drei Eigenschaften: Es lädt in unter 1,5 Sekunden, zeigt die vier Survival-Metriken ohne Scrollen, und das SQL hinter jeder Kachel ist kurz genug, um es auf einem Bildschirm zu lesen. Ich nutze TanStack Table für die Kohorten-Grid, TanStack Start Server Functions für die Datenebene und D1 als Warehouse. Die Abfrage unten erzeugt die wöchentliche MRR-Bewegungs-Kachel, die oben in jedem Dashboard sitzt.

sql
-- Wöchentliche MRR-Bewegung: Neu + Expansion - Kontraktion - Churn.
-- Indiziert nach (week_start, mrr_cents). Aufbereitet durch einen nächtlichen D1-Batch-Job.

WITH weekly AS (
  SELECT
    strftime('%Y-%W', occurred_at) AS week,
    SUM(CASE WHEN event_name = 'plan_upgraded' AND mrr_delta_cents > 0
        THEN mrr_delta_cents ELSE 0 END) AS expansion_cents,
    SUM(CASE WHEN event_name = 'plan_upgraded' AND mrr_delta_cents < 0
        THEN ABS(mrr_delta_cents) ELSE 0 END) AS contraction_cents,
    SUM(CASE WHEN event_name = 'subscription_started'
        THEN mrr_delta_cents ELSE 0 END) AS new_cents,
    SUM(CASE WHEN event_name = 'subscription_canceled'
        THEN ABS(mrr_delta_cents) ELSE 0 END) AS churn_cents
  FROM events
  WHERE occurred_at >= datetime('now', '-90 days')
  GROUP BY week
)
SELECT
  week,
  (new_cents + expansion_cents - contraction_cents - churn_cents) AS net_new_cents,
  new_cents,
  expansion_cents,
  contraction_cents,
  churn_cents
FROM weekly
ORDER BY week DESC
LIMIT 13;

Die Abfrage läuft in unter 80ms auf D1 mit dem Index auf (occurred_at), der den Datumsbereich abdeckt. Das Ergebnis speist eine TanStack Table mit dreizehn Zeilen (eine pro Woche), fünf Spalten (Neu, Expansion, Kontraktion, Churn, Netto-Neu) und einer Sparkline aus net_new_cents. Die Komponente findet sich im TanStack Ship Metrics Reference Repo. Für die breiteren Abfragemuster vgl. den UTM-Attributionsleitfaden; für die Datenebene den SaaS-Datenbank-Architekturleitfaden.

Attribution und das Abstimmungsdebakel, das ich durchgestanden habe

Der einzelne Analytics-Fehlermodus, den ich bei Solo-SaaS am häufigsten sehe, ist Kanal-Doppelzählung: Cloudflare Analytics sagt, ein Kanal habe den Signup getrieben, Stripes Kundenmetadaten sagen einen anderen, und der Founder hat keine Ahnung, welchem er vertrauen soll. Die Lösung ist eine deterministische Attributionsregel und ein nächtlicher Abgleich-Job. First-Touch-Attribution nach utm_source, gespeichert in der Nutzerzeile zum Signup-Zeitpunkt und gespiegelt in die Stripe-Kundenmetadaten. Die Daten leben an drei Orten – URL, Nutzerzeile, Stripe-Kunde – sodass eine Debugging-Session rekonstruieren kann, was passiert ist.

Das Abstimmungsdebakel

Im Q1 2026 führte eine Cloudflare-Analytics-Konfigurationsänderung dazu, dass organische Signups sechs Tage lang einem bezahlten Kanal zugeschrieben wurden – bei einem meiner Produkte. Die Stripe-MRR-Kachel sah großartig aus; die tatsächliche CAC-Kachel sagte die Wahrheit. Der Abgleich-Job kreuzkontrolliert Cloudflare-Analytics-Zahlen gegen die Nutzerzeile utm_source gegen die Stripe-Kundenmetadaten und markiert jede Zeile, in der die drei nicht übereinstimmen. Die Lösung war ein einzelner Config-Rollback und ein Absatz in einem Runbook-Eintrag. Der Job findet sich im TanStack Ship Metrics Reference Repo.

Die Lektion, die überlebt hat: Vertrauen Sie niemals einer einzelnen Quelle der Wahrheit für Umsatzattribution. Der Abgleich-Job ist die zweite Quelle; die menschliche Prüfung des Abgleich-Jobs ist die dritte. Beide müssen klaren, bevor die monatliche CAC-Zahl veröffentlicht wird.

Was ich nicht getestet habe: Ehrliche Grenzen eines Analytics-Stacks 2026

  • Keine Daten im Bereich 1 Mio. $ ARR. Alle zwölf Apps liegen unter ca. 50.000 $ MRR. Analytics-Anforderungen ändern sich über dieser Schwelle – Finance-Grade Reporting, Audit-Trails, benutzerdefinierte Retentionkurven – und ich habe in dieser Größenordnung nicht operiert.
  • Kein Enterprise-Finance-Reporting. Die MRR-Bewegungs-Abfrage reicht für einen Founder; sie würde einen Controller einer öffentlichen Gesellschaft nicht befriedigen.
  • Selbst-gehostete PostHog-Zahlen stammen aus nur zwei Deployments. Der operative Overhead ist real.
  • Stack-Annahmen. Der Code nimmt TanStack Start + Cloudflare Workers + D1 an. Die Architektur überträgt sich auf andere Edge-Runtimes, aber die SQL-Dialekte ändern sich.
  • Keine Paid Ads über 5.000 $/Monat. Meine CAC-Zahlen stammen aus neun Kampagnen unter dieser Obergrenze. Ökonomien bei höherem Spend sind anders geartet.

Die Implikation: Dieser Leitfaden ist der richtige Ausgangspunkt für Solo-SaaS bis ca. 50.000 $ MRR; nicht für ein Series-A-Startup, das ab Tag eins SOC-2-Reporting braucht. Dafür ist der Weg ein gehostetes Warehouse plus ein Finance-Grade-Reporting-Tool.


Sie möchten den vollständigen Analytics-Stack in einer ausrollbaren TanStack Start-App verdrahtet haben? Das Metrics-Modul, der validierte Event-Tracker, das MRR-Bewegungs-Dashboard und der Attributions-Abgleich-Job sind alle ausgeliefert im TanStack Ship Boilerplate. Vgl. die Features-Seite für den Lieferumfang, oder lesen Sie den SaaS-Architektur-Leitfaden 2026 für den breiteren Stack. TanStack Ship ist mein kostenpflichtiges Produkt; die Alternativen (Vergleichsleitfäden) sind für eine ehrliche Evaluation verlinkt.