Tips & Tricks (Atualizado: 02/06/2026)

Teste A/B com Claude Code para SaaS e monetização de blogs

Use Claude Code para criar testes A/B com hipótese, eventos, split no servidor, SQL, privacidade, guardrails e rollback.

Teste A/B com Claude Code para SaaS e monetização de blogs

Comece pela hipótese, não pelo componente

Teste A/B não é apenas alternar duas telas. Para um SaaS ou um blog monetizado, ele responde se uma mudança melhora cadastro, intenção de compra, clique de afiliado, receita de anúncios ou pedido de consultoria sem prejudicar o resto do funil. Claude Code gera a UI rapidamente, mas um experimento confiável precisa de hipótese, atribuição estável, schema de eventos, tamanho de amostra, métricas de proteção, privacidade e rollback.

Em termos simples: variante é a versão testada; exposição é o momento em que o usuário vê a versão e isso é registrado; guardrail é uma métrica que não pode piorar, como LCP, erros ou cliques de intenção de compra; falso positivo é uma vitória aparente causada por ruído, amostra pequena ou checagens repetidas.

O prompt inicial deve trazer a pergunta de negócio:

Crie um fluxo de teste A/B para um SaaS/blog em Next.js App Router.
O objetivo é monetização, não cliques de vaidade.

experiment id: pricing_page_offer_2026_06
hipótese: trocar o CTA de pricing de "Start free trial" para "Start with the free plan" aumenta o início de cadastros sem reduzir cliques de intenção de compra.
métrica principal: signup_start_rate
guardrails: purchase_link_click_rate, p75 LCP, JavaScript error rate
entregáveis: schema de eventos, atribuição no servidor, riscos de Cookie/localStorage, SQL estilo BigQuery, verificação Playwright e checklist de rollout/rollback.

Use exemplos reais: CTA de preço em SaaS, posição de bloco afiliado, formulário de newsletter, etapa de onboarding e caixa de consultoria. Para base de flags, veja feature flags com Claude Code; para eventos, analytics com Claude Code.

CasoMétrica principalGuardrailsFalha comum
CTA de pricing SaaSInício de cadastroIntenção de compra, erros, LCPMais cadastros com menor qualidade comercial
Bloco afiliado no blogClique no produtoLeitura completa, rejeição, velocidadeBloco cedo demais reduz confiança
NewsletterAssinaturas concluídasSpam, descadastrosLista cresce, qualidade cai
OnboardingPrimeiro sucessoTickets, ativaçãoCurto prazo esconde churn

Trave o schema de eventos antes da UI

O erro caro é lançar e descobrir que os dados não juntam. Se o mesmo clique aparece como button_click, ctaClicked e signup_click, a análise vira limpeza manual. Peça a Claude Code um contrato tipado antes dos componentes. Se usa Google Analytics, leia a referência oficial de eventos GA4 e parâmetros do Google tag.

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

Não envie email, nome, empresa ou texto livre em eventos. Quando houver exigência de consentimento para analytics ou publicidade, inicialize o consentimento antes dos tags. O Google documenta isso no guia oficial de consent mode.

Faça a atribuição no servidor

localStorage é fácil, mas causa flicker no primeiro render, mudanças antes/depois do login, reset em navegação privada e problemas quando o storage é bloqueado. A MDN descreve localStorage como armazenamento por origem que persiste entre sessões, veja MDN localStorage. Isso não o torna ideal para o primeiro render.

No Next.js App Router, um Route Handler é um começo seguro. A documentação oficial de route.ts explica handlers com Web Request/Response APIs. Cookies podem ser definidos com NextResponse. Se precisar de lógica de borda, lembre que Next.js 16 renomeou Middleware para Proxy; veja 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;
}

Cookies também exigem cuidado. O guia da MDN sobre configuração segura de cookies cobre Secure, HttpOnly e SameSite. SaaS logado pode usar ID hasheado; blog público pode usar cookie anônimo curto; publicidade deve respeitar CMP e regras locais.

Separe experimento, rollout e rollback

O código pode estar em produção, mas a exposição deve ser controlada sem redeploy. Vercel oferece Vercel Flags; um YAML explícito já resolve o primeiro ciclo.

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

Defina rollback antes do início. Se erros subirem, LCP piorar ou intenção de compra cair, zere a exposição e preserve logs. Depois escale de 10% para 50% e só então 100%.

Analise a partir da exposição

O denominador é quem viu uma variante. Usuários que não foram expostos não entram. Usuários com múltiplas variantes devem ser excluídos ou investigados. No BigQuery, SAFE_DIVIDE evita divisão por zero.

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

Tamanho de amostra e janela de observação vêm antes do lançamento. Olhar todo dia e parar quando a nova versão parece ganhar aumenta falso positivo. Muitas variantes, muitos segmentos, troca de métrica principal e campanha paga simultânea também distorcem a leitura.

Verifique com Playwright

Antes de publicar, confirme que o mesmo ID anônimo recebe a mesma variante, que experimentos desconhecidos retornam erro e que o CTA aparece uma vez. Playwright documenta test e expect e assertions com retry automático.

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

Na prática de Masa com CTAs de artigo, o maior ganho veio da tabela de eventos antes da UI. Definir quais cliques contam como CTA de monetização, quando registrar exposição e como tratar usuários em duas variantes reduziu retrabalho. Um teste Playwright também pegou flicker de localStorage, levando a implementação de volta para cookie no servidor.

Para aplicar em um funil real, veja Claude Code training e product templates. O objetivo é responder perguntas de receita com segurança, não apenas criar mais experimentos.

#Claude Code #Teste A/B #SaaS #monetização #Next.js #analytics
Grátis

PDF grátis: cheatsheet do Claude Code

Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.

Cuidamos dos seus dados e não enviamos spam.

Masa

Sobre o autor

Masa

Engenheiro focado em workflows práticos com Claude Code.