Flimmerfreie clientseitige A/B-Tests

Webfunktionen: blocking="render"

Das Problem

Clientseitige A/B-Testtools laden ein Script, das das DOM ändert, nachdem der Browser bereits mit dem Erstellen der Seite begonnen hat. Ohne Eingriff sieht der Nutzer kurz den Originalinhalt, bevor er zur Testvariante wechselt. Testplattformen haben dieses Problem bisher mit „Snippets gegen Seitenflackern“ umgangen, die die gesamte Seite mit opacity: 0 ausblenden, bis das Testskript abgeschlossen ist oder ein beliebiges Zeitlimit (normalerweise 4 Sekunden) abläuft. Bei diesem Ansatz wird auf progressives Rendering verzichtet, versehentliche Klicks auf unsichtbare Inhalte sind möglich und es werden unnötige Paint-Zyklen eingeführt.

Die Lösung

Mit dem Attribut blocking=render können <script>- oder <link>-Elemente, die im <head> platziert sind, das Rendering (aber nicht das Parsen) blockieren, bis die Ressource abgerufen und ausgeführt wurde. Dadurch wird das Rendering von Testskripts genauso blockiert wie das von Stylesheets. Der Browser rendert die Seite also erst, wenn die Testvariante angewendet wurde. Keine Hacks für die Deckkraft, keine willkürlichen Zeitüberschreitungen und kein Flackern.

Implementierungsstrategie

  1. ERFORDERLICH: Fügen Sie das Test-Script im Dokument <head> ein und fügen Sie blocking="render" hinzu.
  2. ERFORDERLICH: Das Script muss entweder type="module" (bevorzugt für Inline-Scripts) oder async (bevorzugt für externe Scripts mit einem src-Wert) enthalten.
  3. EMPFOHLEN: Halten Sie das Testskript klein und schnell. Da das Rendering blockiert wird, bis das Script ausgeführt wird, verzögert ein großes oder langsames Script direkt das erste Rendern.
  4. Verwenden Sie blocking="render" NICHT für Skripts, die nicht vor dem ersten Rendern ausgeführt werden müssen. Es ist nur für Skripts gedacht, deren Ausgabe beim ersten Rendern sichtbar sein muss.
  5. Wenden Sie blocking="render" NICHT auf Scripts außerhalb von <head> an. Nur Skripts im <head> können das Rendering blockieren.

Implementierungsleitfaden

Grundlegende Einrichtung

OBLIGATORISCH: Laden Sie das Testskript sowohl mit async als auch mit blocking="render". Das Attribut async sorgt dafür, dass das Script das HTML-Parsing nicht blockiert. Der Browser erstellt das DOM weiter, während das Script abgerufen wird. Das Attribut blocking="render" sorgt dafür, dass der Browser erst dann etwas darstellt, wenn das Script ausgeführt wurde.

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

Testspezifische Stile werden geladen

Wenn für den Test ein Varianten-Stylesheet erforderlich ist, verwenden Sie blocking="render" für das <link>-Element. Stylesheets im <head> blockieren das Rendering standardmäßig, dynamisch eingefügte Stylesheets oder solche, die über ein Script hinzugefügt wurden, jedoch nicht. Verwenden Sie blocking="render" explizit, wenn das Stylesheet dynamisch hinzugefügt wird oder wenn Sie die Absicht explizit angeben möchten.

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

Wenn die Testlogik einfach genug ist, um inline eingefügt zu werden, verwenden Sie ein Inline-Modulskript mit blocking="render". Das ist nützlich, wenn die Testlogik eine Konfiguration abruft und DOM-Änderungen direkt anwendet.

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

  • OBLIGATORISCH: Das Testskript MUSS schnell ausgeführt werden. Ein langsames Script verzögert das gesamte Rendering. Legen Sie ein Leistungsbudget (z.B. unter 100 ms Ausführungszeit) für das renderblockierende Script fest.
  • DO split heavy experiment logic from the render-blocking script. Laden Sie einen kleinen, renderblockierenden Stub, der die Variante anwendet, und laden Sie dann umfangreichere Tracking- oder Analysescripts separat mit async oder defer (ohne blocking="render").
  • Verwenden Sie ein Datenattribut für <html> oder <body>, um die aktive Variante zu kennzeichnen, und verwenden Sie CSS-Selektoren, die auf dieses Attribut abgestimmt sind, um die Variante zu gestalten. Dadurch wird eine direkte DOM-Manipulation im renderblockierenden Script vermieden.
  • Verwenden Sie blocking="render" NICHT für Analyse-, Tracking- oder andere nicht visuelle Skripts. Nur Scripts, die die Darstellung beim ersten Rendern ändern müssen, sollten das Rendern blockieren.
  • Kombinieren Sie blocking="render" NICHT mit alten Snippets gegen Seitenflackern. Sie lösen dasselbe Problem. Wenn Sie beide verwenden, kommt es zu unnötigen Verzögerungen.

Fallback-Strategien

Das Attribut blocking wird nicht in allen Browsern unterstützt. In Browsern, die das Attribut nicht unterstützen, wird es ignoriert und das Script wird mit seinem Standardverhalten geladen (async in den obigen Beispielen), was zu Flackern führen kann. Ein Fallback ist erforderlich, um ein Flackern in nicht unterstützten Browsern zu verhindern.

Fallback: Snippet gegen Seitenflackern

DO: Verwenden Sie ein einfaches Snippet gegen Seitenflackern als Fallback nur, wenn blocking="render" nicht unterstützt wird. Unterstützung für die Feature-Erkennung und Überspringen des Fallbacks in Browsern, die dies nativ unterstützen.

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

Weitere Überlegungen

  1. Auswirkungen auf die Leistung: blocking="render" tauscht die Geschwindigkeit des ersten Renderns gegen visuelle Korrektheit ein. Behalten Sie den Largest Contentful Paint (LCP) und den First Contentful Paint (FCP) im Auge, um sicherzustellen, dass das Testskript keine übermäßige Verzögerung verursacht.
  2. Zuverlässigkeit von Drittanbieter-Skripts: Wenn das Test-SDK auf einem Drittanbieter-CDN gehostet wird, kann ein CDN-Ausfall das Rendering vollständig blockieren. Der Browser wendet seine eigenen Zeitlimit-Heuristiken an (die länger als 4 Sekunden sein können), aber es gibt kein vom Entwickler gesteuertes Zeitlimit für blocking="render". Achten Sie darauf, dass der Drittanbieter eine hohe Verfügbarkeit garantiert.
  3. Serverseitige Alternativen: Bei leistungskritischen Seiten sollten Sie serverseitige A/B-Tests (bei denen der Server die richtige Variante direkt rendert) anstelle von clientseitigen Tests in Betracht ziehen. Bei serverseitigen Ansätzen wird das Flackern vollständig eliminiert, ohne dass das Rendering blockiert wird. Verwenden Sie clientseitige blocking="render" nur, wenn serverseitige Tests nicht möglich sind.