Tips & Tricks (Aktualisiert: 2.6.2026)

A/B-Testing mit Claude Code für SaaS und Blog-Monetarisierung

Mit Claude Code sichere A/B-Tests bauen: Hypothese, Events, Server-Split, SQL, Datenschutz, Guardrails und Rollback.

A/B-Testing mit Claude Code für SaaS und Blog-Monetarisierung

Erst die Hypothese, dann der Code

A/B-Testing bedeutet nicht, zwei Seiten zufällig auszuspielen. Für ein SaaS-Produkt oder einen monetarisierten Blog ist es ein kontrolliertes Experiment: Verbessert eine Änderung Registrierung, Kaufabsicht, Affiliate-Klicks, Anzeigenumsatz oder Beratungsanfragen, ohne andere Teile des Funnels zu beschädigen? Claude Code kann die UI-Variante schnell erzeugen. Wertvoll wird der Test aber erst mit Hypothese, stabiler Zuweisung, Event-Schema, Stichprobendisziplin, Guardrail-Metriken, Datenschutz und Rollback.

Die Begriffe in einfacher Sprache: Eine Variante ist die getestete Version. Exposure ist der Moment, in dem ein Nutzer eine Variante sieht und dies erfasst wird. Eine Guardrail-Metrik darf nicht schlechter werden, etwa LCP, Fehlerrate oder Kaufabsicht-Klicks. Ein False Positive ist ein scheinbarer Gewinn, der nur durch Zufall, zu frühes Stoppen oder zu viele Segmente entsteht.

Der erste Prompt an Claude Code sollte so aussehen:

Entwirf einen A/B-Testing-Workflow für ein Next.js App Router SaaS/Blog.
Ziel ist Monetarisierung, nicht Vanity-Klicks.

experiment id: pricing_page_offer_2026_06
Hypothese: Der Pricing-CTA "Start with the free plan" erhöht Signup-Starts gegenüber "Start free trial", ohne Kaufabsicht-Klicks zu senken.
Primärmetrik: signup_start_rate
Guardrails: purchase_link_click_rate, p75 LCP, JavaScript error rate
Lieferumfang: Event-Schema, serverseitige Zuweisung, Cookie/localStorage-Risiken, BigQuery-SQL, Playwright-Prüfung, Rollout/Rollback-Checkliste.

Gib mindestens drei reale Fälle mit. Für SaaS: Pricing-CTA, Onboarding-Schritte, Trial-to-Paid-Hinweis. Für Blogs: Affiliate-Block, Newsletter-Formular, Anzeigenposition. Für Produkt- oder Beratungstrichter: Reihenfolge von Freebie, Produktkarte und Buchungs-CTA. Mehr zu Flags steht in Feature Flags mit Claude Code, mehr zu Messung in Analytics mit Claude Code.

FallPrimärmetrikGuardrailsHäufiger Fehler
SaaS Pricing-CTASignup-StartsKaufabsicht, Fehler, LCPMehr Signups, aber weniger zahlungsbereite Nutzer
Blog Affiliate-BlockProduktlink-KlicksLesefortschritt, Bounce, SpeedMonetarisierung steht zu früh und senkt Vertrauen
Newsletter-FormularAbgeschlossene AnmeldungenSpam, AbmeldungenMenge steigt, Listenqualität sinkt
OnboardingErster ErfolgSupport-Tickets, AktivierungKurzfristige Abschlüsse verdecken späteren Churn

Event-Schema vor der UI fixieren

Der teuerste Fehler ist ein Test, dessen Daten nicht auswertbar sind. Wenn derselbe Klick button_click, ctaClicked und signup_click heißt, wird die Analyse manuell. Lass Claude Code zuerst einen typisierten Event-Vertrag schreiben. Bei Google Analytics sind die offiziellen GA4 Events und die Google tag Parameter die Referenz.

// lib/experiment-events.ts
export type ExperimentId = "pricing_page_offer_2026_06";
export type VariantId = "control" | "free_plan_copy";

export type ExperimentEvent =
  | {
      event_name: "experiment_exposure";
      experiment_id: ExperimentId;
      variant: VariantId;
      anonymous_id: string;
      page_path: string;
    }
  | {
      event_name: "cta_click";
      experiment_id: ExperimentId;
      variant: VariantId;
      anonymous_id: string;
      cta_id: "pricing_primary" | "article_bottom" | "sidebar_offer";
      page_path: string;
    }
  | {
      event_name: "purchase_link_click";
      experiment_id: ExperimentId;
      variant: VariantId;
      anonymous_id: string;
      product_id: string;
      value_usd: number;
      page_path: string;
    }
  | {
      event_name: "guardrail_metric";
      experiment_id: ExperimentId;
      variant: VariantId;
      anonymous_id: string;
      metric_name: "lcp_ms" | "js_error" | "bounce";
      value: number;
      page_path: string;
    };

declare global {
  interface Window {
    gtag?: (command: "event", name: string, params: Record<string, unknown>) => void;
  }
}

export function trackExperimentEvent(event: ExperimentEvent) {
  if (typeof window === "undefined") return;

  window.gtag?.("event", event.event_name, {
    experiment_id: event.experiment_id,
    variant: event.variant,
    anonymous_id: event.anonymous_id,
    page_path: event.page_path,
    ...event,
  });
}

Sende keine E-Mail-Adressen, Namen, Firmennamen oder freien Nutzereingaben als Event-Parameter. Wenn Analytics- oder Werbespeicherung zustimmungspflichtig ist, initialisiere den Consent vor dem Senden. Google beschreibt dies im offiziellen Consent Mode Guide.

Varianten serverseitig zuweisen

Nur localStorage zu verwenden ist einfach, aber riskant: Flackern beim ersten Render, andere Varianten nach Login, Reset im privaten Modus, blockierter Speicher und unzuverlässige Bot-Signale. MDN beschreibt localStorage als origin-gebundenen Speicher, der über Browser-Sitzungen erhalten bleibt. Siehe MDN localStorage. Für den ersten Server-Render ist das keine stabile Wahrheit.

Mit Next.js App Router ist ein Route Handler ein guter Start. Die offizielle route.ts Dokumentation erklärt Handler mit Web Request/Response APIs. Cookies setzt du über NextResponse. Wenn du Edge-Rewrites brauchst: Next.js 16 hat Middleware in Proxy umbenannt, siehe proxy.js.

// app/api/experiments/assign/route.ts
import { NextRequest, NextResponse } from "next/server";

export const runtime = "edge";

type Variant = "control" | "free_plan_copy";

const EXPERIMENTS = {
  pricing_page_offer_2026_06: {
    cookieName: "ab_pricing_page_offer_2026_06",
    variants: [
      { id: "control", weight: 50 },
      { id: "free_plan_copy", weight: 50 },
    ] satisfies Array<{ id: Variant; weight: number }>,
  },
};

function hashToBucket(input: string) {
  let hash = 2166136261;
  for (let index = 0; index < input.length; index += 1) {
    hash ^= input.charCodeAt(index);
    hash = Math.imul(hash, 16777619);
  }
  return Math.abs(hash) % 100;
}

function chooseVariant(experimentId: keyof typeof EXPERIMENTS, anonymousId: string): Variant {
  const experiment = EXPERIMENTS[experimentId];
  const bucket = hashToBucket(`${experimentId}:${anonymousId}`);
  let cumulative = 0;

  for (const variant of experiment.variants) {
    cumulative += variant.weight;
    if (bucket < cumulative) return variant.id;
  }

  return experiment.variants[0].id;
}

export async function GET(request: NextRequest) {
  const experimentId = request.nextUrl.searchParams.get("experiment");

  if (experimentId !== "pricing_page_offer_2026_06") {
    return NextResponse.json({ error: "Unknown experiment" }, { status: 404 });
  }

  const experiment = EXPERIMENTS[experimentId];
  const testAnonymousId = request.headers.get("x-test-anonymous-id");
  const existingCookie = request.cookies.get(experiment.cookieName)?.value;
  const anonymousId = testAnonymousId ?? existingCookie ?? crypto.randomUUID();
  const variant = chooseVariant(experimentId, anonymousId);

  const response = NextResponse.json({
    experimentId,
    variant,
    anonymousId,
  });

  response.cookies.set(experiment.cookieName, anonymousId, {
    httpOnly: true,
    sameSite: "lax",
    secure: process.env.NODE_ENV === "production",
    path: "/",
    maxAge: 60 * 60 * 24 * 30,
  });

  return response;
}

Auch Cookies sind kein Freifahrtschein. MDN erklärt in secure cookie configuration Secure, HttpOnly und SameSite. Ein eingeloggtes SaaS kann eine gehashte User-ID verwenden; ein öffentlicher Blog eher ein kurzlebiges anonymes Cookie; bei Werbung muss der Consent-Status Vorrang haben.

Rollout und Rollback getrennt steuern

Der Code darf live sein, aber die Exposition muss steuerbar bleiben. Vercel bietet Vercel Flags; für den Anfang reicht oft YAML.

# config/experiments.yaml
experiments:
  pricing_page_offer_2026_06:
    status: running
    owner: masa
    hypothesis: "Free-plan copy increases signup starts without hurting paid intent."
    allocation_percent: 50
    variants:
      control: 50
      free_plan_copy: 50
    primary_metric: signup_start_rate
    guardrails:
      - purchase_link_click_rate
      - p75_lcp_ms
      - js_error_rate
    rollback:
      if_js_error_rate_increases_by: 0.02
      if_p75_lcp_ms_worse_by_ms: 300
      action: "set allocation_percent to 0 and keep logging exposure for audit"

Rollback-Regeln müssen vor dem Start stehen. Steigen Fehler, verschlechtert sich LCP oder sinkt Kaufabsicht, setze die Exposition auf 0 und behalte Logs. Danach rollst du kontrolliert von 10% auf 50% und erst dann auf 100% aus.

Von Exposure aus analysieren

Die Exposure ist der Nenner. Nutzer ohne Variante gehören nicht in den Test. Nutzer mit mehreren Varianten werden ausgeschlossen oder untersucht. In BigQuery verhindert SAFE_DIVIDE Fehler durch Division durch null.

-- BigQuery Standard SQL
WITH exposure_raw AS (
  SELECT
    anonymous_id,
    experiment_id,
    ARRAY_AGG(variant ORDER BY event_timestamp LIMIT 1)[OFFSET(0)] AS variant,
    MIN(event_timestamp) AS first_exposed_at,
    COUNT(DISTINCT variant) AS variant_count
  FROM `project.dataset.events`
  WHERE event_name = 'experiment_exposure'
    AND experiment_id = 'pricing_page_offer_2026_06'
  GROUP BY anonymous_id, experiment_id
),
exposure AS (
  SELECT anonymous_id, experiment_id, variant, first_exposed_at
  FROM exposure_raw
  WHERE variant_count = 1
),
events_after_exposure AS (
  SELECT
    e.variant,
    e.anonymous_id,
    ev.event_name,
    ev.value_usd,
    ev.value_ms
  FROM exposure e
  LEFT JOIN `project.dataset.events` ev
    ON ev.anonymous_id = e.anonymous_id
   AND ev.experiment_id = e.experiment_id
   AND ev.event_timestamp >= e.first_exposed_at
)
SELECT
  variant,
  COUNT(DISTINCT anonymous_id) AS exposed_users,
  COUNT(DISTINCT IF(event_name = 'cta_click', anonymous_id, NULL)) AS cta_users,
  SAFE_DIVIDE(
    COUNT(DISTINCT IF(event_name = 'cta_click', anonymous_id, NULL)),
    COUNT(DISTINCT anonymous_id)
  ) AS cta_click_rate,
  COUNT(DISTINCT IF(event_name = 'purchase_link_click', anonymous_id, NULL)) AS purchase_intent_users,
  SAFE_DIVIDE(
    COUNT(DISTINCT IF(event_name = 'purchase_link_click', anonymous_id, NULL)),
    COUNT(DISTINCT anonymous_id)
  ) AS purchase_intent_rate,
  AVG(IF(event_name = 'guardrail_metric' AND value_ms IS NOT NULL, value_ms, NULL)) AS avg_guardrail_ms,
  SUM(IF(event_name = 'guardrail_metric' AND value_usd IS NOT NULL, value_usd, 0)) AS revenue_proxy_usd
FROM events_after_exposure
GROUP BY variant
ORDER BY variant;

Die Stichprobengröße wird vor dem Start festgelegt. Tägliches Reinschauen und Stoppen beim ersten guten Zwischenstand erzeugt False Positives. Auch zu viele Varianten, zu viele Segmente, eine nachträglich geänderte Primärmetrik oder eine gleichzeitige Paid-Kampagne verzerren das Ergebnis.

Mit Playwright prüfen

Vor Veröffentlichung prüfst du: dieselbe anonyme ID bekommt dieselbe Variante, unbekannte Experimente liefern 404, der Monetarisierungs-CTA erscheint genau einmal. Playwright dokumentiert test und expect sowie Assertions mit automatischem Retry.

// tests/experiments.spec.ts
import { test, expect } from "@playwright/test";

test.describe("pricing_page_offer_2026_06", () => {
  test("keeps assignment stable for the same anonymous id", async ({ request, baseURL }) => {
    const url = `${baseURL}/api/experiments/assign?experiment=pricing_page_offer_2026_06`;
    const headers = { "x-test-anonymous-id": "demo-user-42" };

    const first = await request.get(url, { headers });
    const second = await request.get(url, { headers });

    expect(first.ok()).toBeTruthy();
    expect(second.ok()).toBeTruthy();
    expect(await first.json()).toMatchObject(await second.json());
  });

  test("rejects unknown experiments", async ({ request, baseURL }) => {
    const response = await request.get(`${baseURL}/api/experiments/assign?experiment=missing`);

    expect(response.status()).toBe(404);
  });

  test("renders one monetization CTA on the pricing page", async ({ page }) => {
    await page.goto("/pricing?e2e_anonymous_id=demo-user-42");

    await expect(page.getByTestId("pricing-cta")).toBeVisible();
    await expect(page.getByTestId("pricing-cta")).toHaveCount(1);
  });
});

In Masas Tests an Artikel-CTAs war der wichtigste Effekt nicht ein schöner Report, sondern weniger Nacharbeit. Sobald Event-Tabelle, Exposure-Zeitpunkt und Multi-Variant-Regel vor der UI standen, erfand Claude Code keine neuen Eventnamen mehr. Ein Playwright-Test fand außerdem ein localStorage-Flackern, sodass die Zuweisung serverseitig per Cookie umgesetzt wurde.

Für reale Funnels helfen Claude Code Training und Product Templates. Ziel ist nicht mehr Experimente, sondern saubere Experimente, die eine Umsatzfrage beantworten, ohne Vertrauen zu beschädigen.

#Claude Code #A/B Testing #SaaS #Monetarisierung #Next.js #Analytics
Kostenlos

Kostenloses PDF: Claude-Code-Cheatsheet

E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.

Wir schützen Ihre Daten und senden keinen Spam.

Masa

Über den Autor

Masa

Engineer für praktische Claude-Code-Workflows und Team-Einführung.