A/B-tests aan de clientzijde zonder flikkering

Webfuncties: blocking="render"

Het probleem

A/B-testtools aan de clientzijde werken door een script te laden dat het DOM aanpast nadat de browser al is begonnen met het samenstellen van de pagina. Zonder tussenkomst ziet de gebruiker de oorspronkelijke content kort voordat deze flikkert of flitst naar de experimentvariant. Testplatforms hebben dit in het verleden omzeild met fragmenten tegen knipperen die de hele pagina verbergen met opacity: 0 totdat het experimentscript is voltooid of een willekeurige time-out (meestal 4 seconden) is verstreken. Deze aanpak gaat ten koste van progressieve weergave, maakt onbedoelde klikken op onzichtbare content mogelijk en introduceert onnodige weergavecycli.

De oplossing

Met het kenmerk blocking=render kan een <script>- of <link>-element dat in de <head> is geplaatst, de weergave (maar niet de parsering) blokkeren totdat de bron is opgehaald en uitgevoerd. Hierdoor krijgen experimentscripts hetzelfde weergaveblokkerende gedrag dat stylesheets standaard hebben. Zo zorgt u dat de browser de pagina pas weergeeft nadat de experimentvariant is toegepast. Geen hacks voor ondoorzichtigheid, geen willekeurige time-outs en geen flikkering.

Implementatiestrategie

  1. VERPLICHT: Plaats het experimentatiescript in het document <head> en voeg blocking="render" toe.
  2. VERPLICHT: Zorg dat het script type="module" (bij voorkeur voor inline scripts) of async (bij voorkeur voor externe scripts met een src-waarde) heeft.
  3. WEL: Houd het experimenteerscript klein en snel. Omdat de weergave wordt geblokkeerd totdat het script is uitgevoerd, vertraagt een groot of traag script de eerste weergave rechtstreeks.
  4. Gebruik blocking="render" NIET voor scripts die niet hoeven te worden uitgevoerd vóór de eerste weergave. Deze is alleen bedoeld voor scripts waarvan de uitvoer zichtbaar moet zijn in de eerste weergave.
  5. Pas blocking="render" NIET toe op scripts buiten de <head>. Alleen scripts in de <head> kunnen de weergave blokkeren.

Implementatiehandleiding

Basisinstellingen

VERPLICHT: Laad het experimentatiescript met zowel async als blocking="render". Het kenmerk async zorgt ervoor dat het script de HTML-parsering niet blokkeert (de browser blijft de DOM bouwen terwijl het script wordt opgehaald). Het kenmerk blocking="render" zorgt ervoor dat de browser niets tekent totdat het script is uitgevoerd.

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

Experimentspecifieke stijlen laden

Als het experiment een variant-stylesheet vereist, gebruikt u blocking="render" voor het element <link>. Stylesheets in de <head> blokkeren de weergave standaard al, maar dynamisch geïnjecteerde stylesheets of stylesheets die via een script zijn toegevoegd, doen dat niet. Gebruik blocking="render" expliciet als de stylesheet dynamisch wordt toegevoegd of als je expliciet wilt zijn over de intentie.

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

Inline experimentscript

Als de experimentlogica licht genoeg is om inline te gebruiken, gebruik je een inline modulescript met blocking="render". Dit is handig als de experimentlogica een configuratie ophaalt en DOM-wijzigingen rechtstreeks toepast.

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

Praktische tips

  • VERPLICHT: Het experimentscript MOET snel worden uitgevoerd. Een traag script vertraagt de weergave van alles. Stel een prestatiebudget in (bijvoorbeeld een uitvoeringstijd van minder dan 100 ms) voor het script dat de weergave blokkeert.
  • WEL: Splits de logica van zware experimenten van het weergaveblokkerende script. Laad een kleine, weergaveblokkerende stub die de variant toepast en laad daarna zwaardere tracking- of analysescripts afzonderlijk met async of defer (zonder blocking="render").
  • Gebruik WEL een gegevenskenmerk op <html> of <body> om de actieve variant aan te geven en gebruik css-kiezers die aan dat kenmerk zijn gekoppeld voor variantstijlen. Zo voorkom je rechtstreekse DOM-manipulatie in het weergaveblokkerende script.
  • Gebruik blocking="render" NIET voor analyse-, tracking- of andere niet-visuele scripts. Alleen scripts die moeten wijzigen wat de gebruiker ziet bij de eerste weergave, mogen de weergave blokkeren.
  • Combineer blocking="render" NIET met verouderde fragmenten tegen knipperen. Ze lossen hetzelfde probleem op. Als je ze allebei gebruikt, leidt dit tot onnodige vertragingen.

Reservestrategieën

Het kenmerk blocking wordt niet in alle browsers ondersteund. In browsers die dit niet ondersteunen, wordt het kenmerk genegeerd en wordt het script geladen met het standaardgedrag (async in de voorbeelden hierboven), wat kan leiden tot flikkering. Een fallback is vereist om flikkering in niet-ondersteunde browsers te voorkomen.

Reserve: fragment tegen knipperen

DOEN: Gebruik een lichtgewicht fragment tegen knipperen als reserveoptie alleen als blocking="render" niet wordt ondersteund. Ondersteuning voor functieherkenning en de fallback overslaan in browsers die dit zelf afhandelen.

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

Andere overwegingen

  1. Impact op prestaties: blocking="render" ruilt de snelheid van de eerste weergave in voor visuele correctheid. Controleer de Largest Contentful Paint (LCP) en First Contentful Paint (FCP) om te zorgen dat het experimentatiescript geen overmatige vertraging veroorzaakt.
  2. Betrouwbaarheid van scripts van derden: Als de experiment-SDK wordt gehost op een CDN van derden, kan een CDN-storing de weergave volledig blokkeren. De browser past zijn eigen heuristiek voor time-outs toe (die langer kan zijn dan 4 seconden), maar er is geen door de ontwikkelaar beheerde time-out voor blocking="render". Zorg dat de externe provider sterke uptimegaranties heeft.
  3. Alternatieven aan de serverzijde: Voor prestatiegevoelige pagina's kunt u A/B-tests aan de serverzijde overwegen (waarbij de server de juiste variant rechtstreeks rendert) in plaats van tests aan de clientzijde. Met benaderingen aan de serverzijde wordt flikkering volledig geëlimineerd zonder dat dit ten koste gaat van de weergave. Gebruik blocking="render" aan de clientzijde alleen als testen aan de serverzijde niet mogelijk is.