Teste A/B do lado do cliente sem oscilação

Recursos da Web: blocking="render"

O problema

As ferramentas de teste A/B do lado do cliente funcionam carregando um script que modifica o DOM depois que o navegador já começou a construir a página. Sem intervenção, o usuário vê brevemente o conteúdo original antes que ele pisque ou mude para a variante do experimento. As plataformas de teste sempre contornaram esse problema com "snippets antioscilação" que ocultam a página inteira com opacity: 0 até que o script do experimento termine ou um tempo limite arbitrário (normalmente 4 segundos) se esgote. Essa abordagem sacrifica a renderização progressiva, permite cliques acidentais em conteúdo invisível e introduz ciclos de pintura desnecessários.

A solução

O atributo blocking=render permite que um elemento <script> ou <link> colocado no <head> bloqueie a renderização, mas não a análise, até que o recurso seja buscado e executado. Isso dá aos scripts de experimento o mesmo comportamento de bloqueio de renderização que as folhas de estilo têm por padrão, garantindo que o navegador nunca mostre a página até que a variante do experimento tenha sido aplicada. Sem hacks de opacidade, sem tempos limite arbitrários e sem oscilação.

Estratégia de implementação

  1. OBRIGATÓRIO: coloque o script de experimento no documento <head> e adicione blocking="render".
  2. OBRIGATÓRIO: verifique se o script tem type="module" (preferencial para scripts inline) ou async (preferencial para scripts externos com um valor src).
  3. Mantenha o script de experimentação pequeno e rápido. Como a renderização é bloqueada até que o script seja executado, um script grande ou lento atrasa diretamente a primeira exibição.
  4. NÃO use blocking="render" em scripts que não precisam ser executados antes da primeira exibição. Ele é destinado apenas a scripts cuja saída precisa estar visível na renderização inicial.
  5. NÃO aplique blocking="render" a scripts fora do <head>. Somente os scripts no <head> podem bloquear a renderização.

Guia de implementação

Configuração básica

OBRIGATÓRIO: carregue o script de experimento com async e blocking="render". O atributo async garante que o script não bloqueie a análise de HTML (o navegador continua criando o DOM enquanto busca o script). O atributo blocking="render" garante que o navegador não mostre nada até que o script seja executado.

<head>
  <!--
    MANDATORY: Both `async` and `blocking="render"` are required.
    - `async`: Prevents parser-blocking, so the DOM is built in parallel.
    - `blocking="render"`: Holds rendering until the script executes,
      ensuring experiment changes are applied before the user sees anything.
  -->
  <script
    src="https://cdn.example.com/experiment-sdk.js"
    async
    blocking="render"
  ></script>
</head>

Carregando estilos específicos do experimento

Se o experimento exigir uma folha de estilo de variante, use blocking="render" no elemento <link>. As folhas de estilo no <head> já bloqueiam a renderização por padrão, mas as folhas de estilo injetadas dinamicamente ou adicionadas por script não. Use blocking="render" explicitamente quando a folha de estilo for adicionada de forma dinâmica ou quando você quiser deixar clara a intenção.

<head>
  <!--
    DO: Use blocking="render" on experiment stylesheets that are
    dynamically inserted or conditionally loaded by the experiment SDK.
    This ensures variant styles are applied before first paint.
  -->
  <link
    rel="stylesheet"
    href="https://cdn.example.com/experiment-variant-b.css"
    blocking="render"
  >
</head>

Script de experimento inline

Se a lógica do experimento for leve o suficiente para ser inline, use um script de módulo inline com blocking="render". Isso é útil quando a lógica do experimento busca uma configuração e aplica mudanças no DOM diretamente.

<head>
  <!--
    DO: Use an inline module script when the experiment logic is small.
    Module scripts are deferred by default (non-parser-blocking),
    and blocking="render" ensures rendering waits for execution.
  -->
  <script type="module" blocking="render">
    // Fetch the experiment configuration from your testing platform.
    const config = await fetch('/api/experiment?id=homepage-cta')
      .then(res => res.json());

    // Apply the variant by setting a data attribute on <html>.
    // CSS rules keyed to this attribute will style the variant.
    document.documentElement.dataset.variant = config.variant;
  </script>

  <style>
    /* Default styles (control group) */
    .cta-button {
      background-color: blue;
    }

    /* Variant B styles, activated by the data attribute */
    [data-variant="b"] .cta-button {
      background-color: green;
    }
  </style>
</head>

Práticas recomendadas

  • OBRIGATÓRIO: o script de experimentação PRECISA ser executado rapidamente. Um script lento atrasa toda a renderização. Defina um montante de desempenho (por exemplo, tempo de execução inferior a 100 ms) para o script que bloqueia a renderização.
  • RECOMENDAMOS separar a lógica de experimentos pesados do script de bloqueio de renderização. Carregue um stub pequeno que bloqueia a renderização e aplica a variante. Em seguida, carregue scripts de acompanhamento ou análise mais pesados separadamente com async ou defer (sem blocking="render").
  • RECOMENDADO: use um atributo de dados em <html> ou <body> para sinalizar a variante ativa e use seletores de CSS vinculados a esse atributo para definir o estilo da variante. Isso evita a manipulação direta do DOM no script que bloqueia a renderização.
  • NÃO use blocking="render" em scripts de análise, rastreamento ou outros scripts não visuais. Somente os scripts que precisam mudar o que o usuário vê na primeira exibição devem bloquear a renderização.
  • NÃO combine blocking="render" com snippets antioscilação legados. Eles resolvem o mesmo problema. Usar os dois cria atrasos desnecessários.

Estratégias de fallback

O atributo blocking não é compatível com todos os navegadores. Em navegadores que não oferecem suporte a ele, o atributo é ignorado e o script é carregado com o comportamento padrão (async nos exemplos acima), o que pode resultar em oscilação. É necessário um substituto para evitar oscilações em navegadores incompatíveis.

Alternativa: snippet antioscilação

FAÇA: use um snippet antioscilação leve como substituto apenas quando o blocking="render" não for compatível. Detecte o suporte ao recurso e pule o substituto em navegadores que o processam de forma nativa.

<head>
  <!--
    DO: Load the experiment script with blocking="render" for
    browsers that support it. This is the preferred approach.
  -->
  <script
    src="https://cdn.example.com/experiment-sdk.js"
    async
    blocking="render"
  ></script>

  <script>
    // DO: Only apply the anti-flicker fallback in browsers
    // that do not support blocking="render".
    if (!Object.hasOwn(HTMLScriptElement.prototype, 'blocking')) {
      // Hide the page until the experiment script runs.
      document.documentElement.classList.add('ab-loading');

      // DO: Set a timeout to reveal the page if the experiment
      // script takes too long. This prevents an indefinitely
      // blank page on slow connections. Adjust the timeout
      // to match your experiment SDK's expected load time.
      setTimeout(() => {
        document.documentElement.classList.remove('ab-loading');
      }, 4000);
    }
  </script>

  <style>
    /*
      DO: Use opacity to hide content during experiment loading.
      This is only applied when blocking="render" is unsupported.
    */
    .ab-loading {
      opacity: 0 !important;
    }
  </style>
</head>
// DO: In your experiment SDK's initialization callback,
// remove the fallback class to reveal the page.
function onExperimentReady() {
  document.documentElement.classList.remove('ab-loading');
}

Outras considerações

  1. Impacto no desempenho: o blocking="render" troca a velocidade da primeira pintura pela correção visual. Monitore a Largest Contentful Paint (LCP) e a First Contentful Paint (FCP) para garantir que o script de experimento não esteja adicionando atrasos excessivos.
  2. Confiabilidade do script de terceiros: se o SDK do experimento estiver hospedado em uma CDN de terceiros, uma interrupção na CDN poderá bloquear totalmente a renderização. O navegador aplica as próprias heurísticas de tempo limite (que podem ser maiores que 4 segundos), mas não há um tempo limite controlado pelo desenvolvedor para blocking="render". Verifique se o provedor terceirizado tem garantias sólidas de tempo de atividade.
  3. Alternativas do lado do servidor: para páginas em que a performance é essencial, considere o teste A/B do lado do servidor (em que o servidor renderiza a variante correta diretamente) em vez do teste do lado do cliente. As abordagens do lado do servidor eliminam completamente o flicker sem nenhum custo de bloqueio de renderização. Use o blocking="render" do lado do cliente somente quando o teste do lado do servidor não for viável.