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.
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.
| Fall | Primärmetrik | Guardrails | Häufiger Fehler |
|---|---|---|---|
| SaaS Pricing-CTA | Signup-Starts | Kaufabsicht, Fehler, LCP | Mehr Signups, aber weniger zahlungsbereite Nutzer |
| Blog Affiliate-Block | Produktlink-Klicks | Lesefortschritt, Bounce, Speed | Monetarisierung steht zu früh und senkt Vertrauen |
| Newsletter-Formular | Abgeschlossene Anmeldungen | Spam, Abmeldungen | Menge steigt, Listenqualität sinkt |
| Onboarding | Erster Erfolg | Support-Tickets, Aktivierung | Kurzfristige 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.
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.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Claude Code Permission Safety Ladder: Zugriff kontrolliert erweitern
Von read-only zu begrenzten Änderungen, Prüfbefehlen und Deploy-Checks mit klarer Kontrolle.
Claude Code Small PR Proof Pack: kleine Änderungen reviewbar machen
Ein Proof Pack für Claude-Code-PRs: Diff, Checks, öffentliche URL, CTA-Pfad und Rollback.
Claude-Code-Review-Gate vor dem Commit
Vor dem Commit mit Claude Code prüfen: Diff, Build, öffentliche URL, Gumroad-Links, Beratung-CTA, fehlende Tests und fremde Dateien.