Test A/B lato client senza sfarfallio

Funzionalità web: blocking="render"

Il problema

Gli strumenti di test A/B lato client funzionano caricando uno script che modifica il DOM dopo che il browser ha già iniziato a creare la pagina. Senza intervento, l'utente vede brevemente i contenuti originali prima che sfarfallino o lampeggino per passare alla variante dell'esperimento. In passato, le piattaforme di test hanno aggirato questo problema con "snippet anti-sfarfallio" che nascondono l'intera pagina con opacity: 0 finché lo script dell'esperimento non termina o non trascorre un timeout arbitrario (in genere 4 secondi). Questo approccio sacrifica il rendering progressivo, consente clic involontari su contenuti invisibili e introduce cicli di visualizzazione non necessari.

La soluzione

L'attributo blocking=render consente a un elemento <script> o <link> inserito in <head> di bloccare il rendering, ma non l'analisi, finché la risorsa non è stata recuperata ed eseguita. In questo modo, gli script di sperimentazione hanno lo stesso comportamento di blocco della visualizzazione dei fogli di stile per impostazione predefinita, garantendo che il browser non mostri mai la pagina finché non viene applicata la variante dell'esperimento. Nessun hack di opacità, nessun timeout arbitrario e nessuno sfarfallio.

Strategia di implementazione

  1. OBBLIGATORIO: inserisci lo script di sperimentazione nel documento <head> e aggiungi blocking="render".
  2. OBBLIGATORIO: assicurati che lo script abbia type="module" (preferito per gli script incorporati) o async (preferito per gli script esterni con un valore src).
  3. SÌ: mantieni lo script di sperimentazione piccolo e veloce. Poiché il rendering è bloccato fino all'esecuzione dello script, uno script di grandi dimensioni o lento ritarda direttamente la prima visualizzazione.
  4. NON utilizzare blocking="render" negli script che non devono essere eseguiti prima del primo paint. È destinato solo agli script il cui output deve essere visibile nel rendering iniziale.
  5. NON applicare blocking="render" agli script esterni a <head>. Solo gli script in <head> possono bloccare il rendering.

Guida all'implementazione

Impostazione di base

OBBLIGATORIO: carica lo script di sperimentazione con async e blocking="render". L'attributo async garantisce che lo script non blocchi l'analisi HTML (il browser continua a creare il DOM durante il recupero dello script). L'attributo blocking="render" garantisce che il browser non mostri nulla finché lo script non è stato eseguito.

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

Caricamento degli stili specifici dell'esperimento

Se l'esperimento richiede un foglio di stile della variante, utilizza blocking="render" nell'elemento <link>. I fogli di stile in <head> bloccano già il rendering per impostazione predefinita, ma i fogli di stile inseriti dinamicamente o aggiunti tramite script non lo fanno. Utilizza blocking="render" in modo esplicito quando il foglio di stile viene aggiunto in modo dinamico o quando vuoi essere esplicito riguardo all'intent.

<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 per esperimenti incorporato

Se la logica dell'esperimento è abbastanza leggera da poter essere incorporata, utilizza uno script del modulo incorporato con blocking="render". Questo è utile quando la logica dell'esperimento recupera una configurazione e applica direttamente le modifiche al DOM.

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

Best practice

  • OBBLIGATORIO: lo script di sperimentazione DEVE essere eseguito rapidamente. Uno script lento ritarda l'intero rendering. Imposta un budget delle prestazioni (ad es. un tempo di esecuzione inferiore a 100 ms) per lo script che blocca il rendering.
  • SÌ: separa la logica degli esperimenti pesanti dallo script che blocca il rendering. Carica uno stub di blocco della visualizzazione di piccole dimensioni che applica la variante, quindi carica separatamente script di monitoraggio o analisi più pesanti con async o defer (senza blocking="render").
  • SÌ: utilizza un attributo di dati su <html> o <body> per segnalare la variante attiva e utilizza i selettori CSS associati a quell'attributo per lo stile della variante. In questo modo si evita la manipolazione diretta del DOM nello script che blocca il rendering.
  • NON utilizzare blocking="render" su script di analisi, monitoraggio o altri script non visivi. Solo gli script che devono modificare ciò che l'utente vede al primo paint devono bloccare il rendering.
  • NON combinare blocking="render" con snippet anti-sfarfallio legacy. Risolvono lo stesso problema; l'utilizzo di entrambi crea ritardi non necessari.

Strategie di fallback

L'attributo blocking non è supportato in tutti i browser. Nei browser che non lo supportano, l'attributo viene ignorato e lo script viene caricato con il comportamento predefinito (async negli esempi precedenti), il che potrebbe causare sfarfallio. È necessario un fallback per evitare lo sfarfallio nei browser non supportati.

Formula di riserva: snippet anti-sfarfallio

SÌ: utilizza uno snippet anti-sfarfallio leggero come opzione di riserva solo quando blocking="render" non è supportato. Rileva il supporto delle funzionalità e ignora il fallback nei browser che lo gestiscono in modo nativo.

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

Altre considerazioni

  1. Impatto sulle prestazioni: blocking="render" sacrifica la velocità di first-paint per la correttezza visiva. Monitora Largest Contentful Paint (LCP) e First Contentful Paint (FCP) per assicurarti che lo script di sperimentazione non aggiunga un ritardo eccessivo.
  2. Affidabilità degli script di terze parti: se l'SDK dell'esperimento è ospitato su una CDN di terze parti, un'interruzione della CDN potrebbe bloccare completamente il rendering. Il browser applica le proprie euristiche di timeout (che potrebbero essere superiori a 4 secondi), ma non esiste un timeout controllato dallo sviluppatore per blocking="render". Assicurati che il fornitore di terze parti offra solide garanzie di uptime.
  3. Alternative lato server: per le pagine in cui le prestazioni sono fondamentali, prendi in considerazione i test A/B lato server (in cui il server esegue il rendering della variante corretta direttamente) anziché i test lato client. Gli approcci lato server eliminano completamente lo sfarfallio senza alcun costo di blocco del rendering. Utilizza blocking="render" lato client solo quando i test lato server non sono fattibili.