Tests A/B côté client sans scintillement

Fonctionnalités Web : blocking="render"

Le problème

Les outils de test A/B côté client chargent un script qui modifie le DOM une fois que le navigateur a déjà commencé à construire la page. Sans intervention, l'utilisateur voit brièvement le contenu d'origine avant qu'il ne clignote ou ne passe à la variante du test. Les plates-formes de test ont toujours contourné ce problème en utilisant des "extraits anti-clignotement" qui masquent l'intégralité de la page avec opacity: 0 jusqu'à ce que le script de test se termine ou qu'un délai arbitraire (généralement quatre secondes) s'écoule. Cette approche sacrifie l'affichage progressif, permet des clics accidentels sur du contenu invisible et introduit des cycles d'affichage inutiles.

La solution

L'attribut blocking=render permet à un élément <script> ou <link> placé dans <head> de bloquer l'affichage (mais pas l'analyse) jusqu'à ce que la ressource ait été récupérée et exécutée. Cela donne aux scripts de test le même comportement de blocage de l'affichage que les feuilles de style par défaut, ce qui garantit que le navigateur n'affiche jamais la page tant que la variante de test n'a pas été appliquée. Pas d'astuces d'opacité, pas de délais d'expiration arbitraires et pas de scintillement.

Stratégie d'implémentation

  1. OBLIGATOIRE : Placez le script de test dans l'élément <head> du document et ajoutez blocking="render".
  2. OBLIGATOIRE : Assurez-vous que le script comporte type="module" (recommandé pour les scripts intégrés) ou async (recommandé pour les scripts externes avec une valeur src).
  3. À FAIRE : veillez à ce que le script de test soit court et rapide. Étant donné que l'affichage est bloqué jusqu'à l'exécution du script, un script volumineux ou lent retarde directement le premier affichage.
  4. N'UTILISEZ PAS blocking="render" sur les scripts qui n'ont pas besoin d'être exécutés avant le premier affichage. Il n'est destiné qu'aux scripts dont la sortie doit être visible dans l'affichage initial.
  5. N'appliquez PAS blocking="render" aux scripts en dehors de <head>. Seuls les scripts de l'élément <head> peuvent bloquer l'affichage.

Guide d'implémentation

Configuration de base

OBLIGATOIRE : Chargez le script de test avec async et blocking="render". L'attribut async garantit que le script ne bloque pas l'analyse HTML (le navigateur continue de créer le DOM pendant qu'il récupère le script). L'attribut blocking="render" garantit que le navigateur n'affiche rien tant que le script n'a pas été exécuté.

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

Chargement des styles spécifiques au test…

Si le test nécessite une feuille de style de variante, utilisez blocking="render" sur l'élément <link>. Les feuilles de style de l'élément <head> bloquent déjà l'affichage par défaut, mais pas les feuilles de style injectées de manière dynamique ou ajoutées via un script. Utilisez blocking="render" de manière explicite lorsque la feuille de style est ajoutée de manière dynamique ou lorsque vous souhaitez être explicite sur l'intention.

<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 test intégré

Si la logique du test est suffisamment légère pour être intégrée, utilisez un script de module intégré avec blocking="render". Cela est utile lorsque la logique du test récupère une configuration et applique directement les modifications du 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>

Bonnes pratiques

  • OBLIGATOIRE : le script de test DOIT s'exécuter rapidement. Un script lent retarde l'ensemble de l'affichage. Définissez un budget de performance (par exemple, un temps d'exécution inférieur à 100 ms) pour le script bloquant l'affichage.
  • À FAIRE : Séparez la logique de test lourde du script bloquant l'affichage. Chargez un petit stub bloquant l'affichage qui applique la variante, puis chargez séparément des scripts de suivi ou d'analyse plus lourds avec async ou defer (sans blocking="render").
  • À FAIRE : Utilisez un attribut de données sur <html> ou <body> pour signaler la variante active, et utilisez des sélecteurs CSS associés à cet attribut pour appliquer un style à la variante. Cela évite la manipulation directe du DOM dans le script bloquant l'affichage.
  • N'UTILISEZ PAS blocking="render" sur les scripts d'analyse, de suivi ou d'autres scripts non visuels. Seuls les scripts qui doivent modifier ce que l'utilisateur voit lors du premier rendu doivent bloquer l'affichage.
  • NE COMBINEZ PAS blocking="render" avec d'anciens extraits anti-clignotement. Ils résolvent le même problème. Utiliser les deux crée des retards inutiles.

Stratégies de remplacement

L'attribut blocking n'est pas compatible avec tous les navigateurs. Dans les navigateurs qui ne le prennent pas en charge, l'attribut est ignoré et le script se charge avec son comportement par défaut (async dans les exemples ci-dessus), ce qui peut entraîner un clignotement. Une solution de remplacement est nécessaire pour éviter le scintillement dans les navigateurs non compatibles.

Fallback: Anti-Flicker Snippet

À FAIRE : Utilisez un extrait anti-clignotement léger comme solution de remplacement uniquement lorsque blocking="render" n'est pas compatible. Détectez la compatibilité avec les fonctionnalités et ignorez la création de remplacement dans les navigateurs qui la gèrent de manière native.

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

Autres points à prendre en compte

  1. Impact sur les performances : blocking="render" privilégie la précision visuelle à la vitesse de premier rendu. Surveillez le Largest Contentful Paint (LCP) et le First Contentful Paint (FCP) pour vous assurer que le script de test n'ajoute pas de délai excessif.
  2. Fiabilité des scripts tiers : si le SDK de test est hébergé sur un CDN tiers, une panne du CDN peut bloquer complètement l'affichage. Le navigateur applique ses propres heuristiques de délai avant expiration (qui peuvent être supérieures à quatre secondes), mais il n'existe pas de délai avant expiration contrôlé par le développeur pour blocking="render". Assurez-vous que le fournisseur tiers offre des garanties de temps d'activité solides.
  3. Alternatives côté serveur : pour les pages où les performances sont cruciales, envisagez d'utiliser des tests A/B côté serveur (où le serveur affiche directement la variante appropriée) plutôt que des tests côté client. Les approches côté serveur éliminent complètement le clignotement sans aucun coût de blocage de l'affichage. N'utilisez le blocking="render" côté client que lorsque les tests côté serveur ne sont pas possibles.