Skip to main content

Visão Geral

Onboarding é uma das etapas mais críticas de qualquer SaaS — a maioria dos churns começa aqui:
  • ✅ Alto impacto em ativação e retenção de longo prazo
  • ✅ Usuários são mais tolerantes a variações durante o setup
  • ✅ Conversão fácil de medir (completou step X, chegou ao aha moment)
Neste guia, você aprenderá a testar:
  1. Número de steps no wizard
  2. Ordem das etapas
  3. Tela de boas-vindas

Exemplo 1: Wizard Curto vs Longo

Testar se um onboarding mais curto aumenta a taxa de conclusão.

Criar o experimento no dashboard

  1. Acesse o dashboard do Testly
  2. Clique em “Novo Experimento”
  3. Nome: onboarding-wizard-length
  4. Variantes:
    • Controle: Wizard completo (5 steps)
    • Variante B: Wizard curto (3 steps essenciais)

Implementação

components/OnboardingWizard.tsx
'use client';

import { useState } from 'react';
import { useExperiment } from '@testlyjs/react';

const fullSteps = [
  { id: 'profile', title: 'Seu Perfil', component: ProfileStep },
  { id: 'team', title: 'Seu Time', component: TeamStep },
  { id: 'first-experiment', title: 'Primeiro Experimento', component: FirstExperimentStep },
  { id: 'sdk-install', title: 'Instalar SDK', component: SDKInstallStep },
  { id: 'complete', title: 'Concluído', component: CompleteStep },
];

const shortSteps = [
  { id: 'first-experiment', title: 'Primeiro Experimento', component: FirstExperimentStep },
  { id: 'sdk-install', title: 'Instalar SDK', component: SDKInstallStep },
  { id: 'complete', title: 'Concluído', component: CompleteStep },
];

export function OnboardingWizard() {
  const { variant, loading, convert } = useExperiment('onboarding-wizard-length');
  const [currentStep, setCurrentStep] = useState(0);

  if (loading) return <WizardSkeleton />;

  const steps = variant === 'variant-b' ? shortSteps : fullSteps;
  const CurrentStepComponent = steps[currentStep].component;

  const handleNext = () => {
    convert(`step_completed_${steps[currentStep].id}`);

    if (currentStep === steps.length - 1) {
      convert('onboarding_completed');
      window.location.href = '/dashboard';
    } else {
      setCurrentStep((prev) => prev + 1);
    }
  };

  const handleSkip = () => {
    convert('onboarding_skipped');
    window.location.href = '/dashboard';
  };

  return (
    <div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
      <div className="bg-white rounded-2xl shadow-lg w-full max-w-xl p-8">
        {/* Progress */}
        <div className="flex items-center gap-2 mb-8">
          {steps.map((step, index) => (
            <div
              key={step.id}
              className={`h-1.5 flex-1 rounded-full transition-colors ${
                index <= currentStep ? 'bg-blue-600' : 'bg-gray-200'
              }`}
            />
          ))}
        </div>

        {/* Step label */}
        <p className="text-sm text-gray-500 mb-2">
          Passo {currentStep + 1} de {steps.length}
        </p>
        <h2 className="text-2xl font-bold text-gray-900 mb-6">
          {steps[currentStep].title}
        </h2>

        {/* Content */}
        <CurrentStepComponent />

        {/* Actions */}
        <div className="flex items-center justify-between mt-8">
          <button
            onClick={handleSkip}
            className="text-sm text-gray-400 hover:text-gray-600"
          >
            Pular por agora
          </button>
          <button
            onClick={handleNext}
            className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2.5 rounded-lg font-semibold"
          >
            {currentStep === steps.length - 1 ? 'Ir para o Dashboard' : 'Continuar'}
          </button>
        </div>
      </div>
    </div>
  );
}
Rastreie cada step individual com convert('step_completed_X'). Isso permite identificar onde os usuários abandonam o onboarding, independentemente do resultado do experimento.

Exemplo 2: Tela de Boas-Vindas

Testar mensagem de boas-vindas personalizada vs genérica.
components/WelcomeScreen.tsx
'use client';

import { useExperiment } from '@testlyjs/react';

interface WelcomeScreenProps {
  userName: string;
  onContinue: () => void;
}

export function WelcomeScreen({ userName, onContinue }: WelcomeScreenProps) {
  const { variant, convert } = useExperiment('onboarding-welcome-message');

  const handleContinue = () => {
    convert('welcome_screen_continued');
    onContinue();
  };

  return (
    <div className="text-center py-8">
      {variant === 'variant-b' ? (
        // Personalizado com nome
        <>
          <div className="text-5xl mb-4">👋</div>
          <h1 className="text-3xl font-bold text-gray-900 mb-3">
            Olá, {userName}!
          </h1>
          <p className="text-gray-600 mb-8 max-w-sm mx-auto">
            Você está a 3 passos de rodar seu primeiro experimento. Vamos lá?
          </p>
        </>
      ) : (
        // Genérico
        <>
          <div className="text-5xl mb-4">🚀</div>
          <h1 className="text-3xl font-bold text-gray-900 mb-3">
            Bem-vindo ao Testly!
          </h1>
          <p className="text-gray-600 mb-8 max-w-sm mx-auto">
            Configure sua conta em poucos passos e comece a testar.
          </p>
        </>
      )}

      <button
        onClick={handleContinue}
        className="bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 rounded-lg font-semibold"
      >
        Começar Setup
      </button>
    </div>
  );
}

Exemplo 3: Checklist vs Wizard

Testar se um checklist livre (vs wizard guiado) aumenta a taxa de ativação.
components/OnboardingChecklist.tsx
'use client';

import { useState } from 'react';
import { useExperiment } from '@testlyjs/react';

const checklistItems = [
  { id: 'create-experiment', label: 'Criar seu primeiro experimento', href: '/experiments/new' },
  { id: 'install-sdk', label: 'Instalar o SDK', href: '/docs/sdk/installation' },
  { id: 'add-variant', label: 'Configurar variantes', href: null },
  { id: 'go-live', label: 'Ativar experimento', href: null },
];

export function OnboardingChecklist() {
  const { variant, convert } = useExperiment('onboarding-format');
  const [completed, setCompleted] = useState<Set<string>>(new Set());

  if (variant !== 'variant-b') {
    // Variante B recebe checklist; controle recebe wizard (outro componente)
    return null;
  }

  const handleItemClick = (itemId: string, href: string | null) => {
    const newCompleted = new Set(completed).add(itemId);
    setCompleted(newCompleted);
    convert(`checklist_item_${itemId}`);

    if (newCompleted.size === checklistItems.length) {
      convert('onboarding_completed');
    }

    if (href) window.location.href = href;
  };

  return (
    <div className="bg-white rounded-xl border border-gray-200 p-6">
      <h2 className="text-xl font-bold text-gray-900 mb-1">Primeiros Passos</h2>
      <p className="text-sm text-gray-500 mb-6">
        {completed.size}/{checklistItems.length} concluídos
      </p>

      <div className="space-y-3">
        {checklistItems.map((item) => {
          const isDone = completed.has(item.id);
          return (
            <button
              key={item.id}
              onClick={() => handleItemClick(item.id, item.href)}
              className="w-full flex items-center gap-3 p-3 rounded-lg hover:bg-gray-50 text-left transition-colors"
            >
              <div
                className={`w-5 h-5 rounded-full border-2 flex items-center justify-center flex-shrink-0 ${
                  isDone
                    ? 'bg-green-500 border-green-500'
                    : 'border-gray-300'
                }`}
              >
                {isDone && (
                  <svg className="w-3 h-3 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
                  </svg>
                )}
              </div>
              <span className={`text-sm font-medium ${isDone ? 'line-through text-gray-400' : 'text-gray-900'}`}>
                {item.label}
              </span>
              {!isDone && item.href && (
                <svg className="w-4 h-4 text-gray-400 ml-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
                </svg>
              )}
            </button>
          );
        })}
      </div>
    </div>
  );
}
Hipótese: Checklist livre dá sensação de controle e pode aumentar engajamento vs wizard linear que força uma ordem.

Métricas de Onboarding

Eventos importantes para rastrear:

step_completed_profile        → usuário completou o perfil
step_completed_sdk-install    → usuário instalou o SDK
step_completed_first-experiment → criou primeiro experimento
onboarding_completed          → concluiu todo o fluxo
onboarding_skipped            → pulou o onboarding

Métricas derivadas (calcule externamente):
  Taxa de conclusão = onboarding_completed / onboarding_started
  Drop-off por step = onde a taxa de step_completed cai
  Time to ativação  = tempo entre signup e primeiro evento real

Próximos Passos

Hero Section

Teste títulos e layouts de hero

Pricing Page

Teste estruturas de preço

Call-to-Action

Otimize botões e formulários

Boas Práticas

Metodologias de experimentação