Tips & Tricks (Diperbarui: 2/6/2026)

A/B Testing dengan Claude Code untuk SaaS dan Monetisasi Blog

Bangun A/B test dengan Claude Code: hipotesis, event schema, server split, SQL, consent, guardrail, dan rollback.

A/B Testing dengan Claude Code untuk SaaS dan Monetisasi Blog

Mulai dari Hipotesis, Bukan Toggle

A/B testing bukan sekadar menampilkan dua versi halaman secara acak. Untuk SaaS atau blog yang dimonetisasi, eksperimen harus menjawab pertanyaan bisnis: apakah perubahan CTA, posisi iklan, link afiliasi, newsletter, atau form konsultasi meningkatkan pendapatan tanpa merusak pengalaman pembaca dan funnel utama? Claude Code bisa menulis toggle dengan cepat, tetapi test yang bisa dipercaya membutuhkan hipotesis, assignment yang stabil, event schema, sample size, guardrail metrics, consent, dan rollback.

Istilahnya sederhana. Variant adalah versi yang diuji. Exposure adalah momen ketika pengguna melihat variant dan dicatat. Guardrail metric adalah angka yang tidak boleh memburuk, seperti LCP, error rate, bounce, atau klik niat beli. False positive adalah kemenangan semu karena noise, sampel kecil, atau terlalu sering mengintip hasil.

Prompt awal untuk Claude Code sebaiknya seperti ini:

Buat workflow A/B testing untuk SaaS/blog di Next.js App Router.
Tujuannya monetisasi, bukan vanity clicks.

experiment id: pricing_page_offer_2026_06
hipotesis: mengubah CTA pricing dari "Start free trial" ke "Start with the free plan" akan menaikkan signup starts tanpa menurunkan paid-intent clicks.
primary metric: signup_start_rate
guardrails: purchase_link_click_rate, p75 LCP, JavaScript error rate
output: event schema, server-side assignment, catatan Cookie/localStorage, SQL gaya BigQuery, verifikasi Playwright, checklist rollout/rollback.

Pakai minimal tiga use case nyata: CTA pricing SaaS, posisi blok afiliasi di artikel, copy newsletter, onboarding step, atau kotak konsultasi. Untuk dasar feature flag, baca feature flags dengan Claude Code. Untuk tracking, baca analytics implementation dengan Claude Code.

Use casePrimary metricGuardrailsKegagalan umum
CTA pricing SaaSSignup start ratePaid clicks, error, LCPSignup naik tapi kualitas pembeli turun
Blok afiliasi blogKlik link produkRead completion, bounce, speedBlok monetisasi terlalu cepat muncul dan mengurangi trust
Form newsletterSubscription selesaiSpam, unsubscribeJumlah naik tapi kualitas list turun
OnboardingFirst success rateSupport ticket, aktivasiSukses awal menutupi churn berikutnya

Kunci Event Schema Sebelum UI

Kesalahan mahal terjadi saat data tidak bisa digabung setelah launch. Jika klik yang sama kadang bernama button_click, ctaClicked, dan signup_click, analisis berubah menjadi kerja manual. Minta Claude Code membuat kontrak event bertipe sebelum membuat komponen. Jika memakai Google Analytics, gunakan referensi resmi GA4 events dan Google tag parameters.

// 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,
  });
}

Jangan kirim email, nama, perusahaan, atau teks bebas pengguna sebagai event parameter. Jika analytics atau advertising storage butuh consent, inisialisasi consent sebelum tag dikirim. Google menjelaskan pola ini di consent mode guide.

Lakukan Assignment di Server

localStorage mudah, tetapi rawan first-render flicker, perubahan variant setelah login, reset di private browsing, dan storage blocking. MDN menjelaskan localStorage sebagai storage per origin yang bertahan antar sesi browser: MDN localStorage. Itu bukan sumber terbaik untuk render pertama.

Di Next.js App Router, Route Handler adalah awal yang praktis. Dokumentasi resmi route.ts menjelaskan custom handler berbasis Web Request/Response APIs. Cookie bisa diatur lewat NextResponse. Jika perlu logic di edge, ingat bahwa Next.js 16 mengganti nama Middleware menjadi Proxy; lihat 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;
}

Cookie juga perlu batasan. MDN menjelaskan Secure, HttpOnly, dan SameSite dalam secure cookie configuration. SaaS login bisa memakai hashed user id; blog publik bisa memakai anonymous cookie pendek; iklan dan analytics harus menghormati CMP dan aturan wilayah.

Pisahkan Experiment, Rollout, dan Rollback

Kode boleh deploy, tetapi exposure harus bisa diubah tanpa redeploy. Vercel punya Vercel Flags; untuk awal, config YAML cukup jelas.

# 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"

Tulis rollback sebelum mulai. Jika error naik, LCP memburuk, atau paid-intent clicks turun, set exposure ke 0 dan simpan log untuk audit. Setelah stabil, naikkan bertahap dari 10%, 50%, lalu 100%.

Analisis dari Exposure

Denominator adalah pengguna yang benar-benar melihat variant. Pengguna tanpa exposure tidak masuk. Pengguna yang melihat beberapa variant harus dikecualikan atau diselidiki. Di BigQuery, SAFE_DIVIDE membantu menghindari pembagian dengan nol.

-- 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;

Sample size ditentukan sebelum launch. Mengintip hasil setiap hari lalu berhenti saat variant baru unggul akan menaikkan false positive. Banyak variant, banyak segment, mengganti primary metric di tengah jalan, atau menjalankan paid campaign bersamaan juga bisa membuat hasil menipu.

Verifikasi dengan Playwright

Sebelum publish, cek bahwa anonymous id yang sama mendapat variant yang sama, experiment id tidak dikenal mengembalikan 404, dan CTA monetisasi hanya render sekali. Playwright mendokumentasikan test dan expect, serta assertions dengan auto-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);
  });
});

Dalam praktik Masa saat mengecek CTA artikel, manfaat terbesar datang dari event table sebelum UI. Menentukan klik mana yang dihitung sebagai monetization CTA, kapan exposure dicatat, dan bagaimana menangani pengguna multi-variant membuat revisi Claude Code jauh berkurang. Satu test kecil juga menangkap flicker akibat localStorage, lalu assignment dipindahkan ke server cookie.

Untuk menerapkan ini ke funnel nyata, lihat Claude Code training dan product templates. Tujuannya bukan memperbanyak eksperimen, tetapi membuat tiap eksperimen menjawab pertanyaan revenue dengan aman.

#Claude Code #A/B testing #SaaS #monetisasi blog #Next.js #analytics
Gratis

PDF gratis: cheatsheet Claude Code

Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.

Kami menjaga datamu dan tidak mengirim spam.

Masa

Tentang penulis

Masa

Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.