A/B-тестирование на стороне клиента без мерцания

Веб-функции: blocking="render"

Проблема

Клиентские инструменты для A/B-тестирования загружают скрипт, который изменяет DOM после того, как браузер уже начал создавать страницу. Без вмешательства пользователь на короткое время видит оригинальный контент, прежде чем он мерцает или мигает, переходя к экспериментальному варианту. Раньше платформы для тестирования решали эту проблему с помощью фрагментов кода, которые скрывали всю страницу с помощью opacity: 0, пока не завершится выполнение скрипта эксперимента или не истечет время ожидания (обычно 4 секунды). При таком подходе невозможна прогрессивная отрисовка, могут происходить случайные клики по невидимому контенту и выполняются лишние циклы отрисовки.

Решение

Атрибут blocking=render позволяет элементу <script> или <link>, размещенному в <head>, блокировать отрисовку (но не синтаксический анализ) до тех пор, пока ресурс не будет получен и выполнен. Это дает скриптам экспериментов такое же поведение блокировки рендеринга, как и у таблиц стилей по умолчанию, гарантируя, что браузер никогда не отрисует страницу до тех пор, пока не будет применен вариант эксперимента. Никаких ухищрений с прозрачностью, произвольных тайм-аутов и мерцания.

Стратегия реализации

  1. ОБЯЗАТЕЛЬНО. Поместите скрипт эксперимента в элемент <head> документа и добавьте blocking="render".
  2. ОБЯЗАТЕЛЬНО. Убедитесь, что в скрипте есть атрибут type="module" (рекомендуется для встроенных скриптов) или async (рекомендуется для внешних скриптов со значением src).
  3. РЕКОМЕНДУЕТСЯ использовать небольшой и быстрый скрипт для экспериментов. Поскольку отрисовка блокируется до выполнения скрипта, большой или медленный скрипт напрямую задерживает первую отрисовку.
  4. НЕ ИСПОЛЬЗУЙТЕ blocking="render" в скриптах, которые не нужно запускать до первой отрисовки. Он предназначен только для скриптов, результат работы которых должен быть виден при начальной отрисовке.
  5. НЕ применяйте blocking="render" к скриптам за пределами <head>. Блокировать отрисовку могут только скрипты в <head>.

Руководство по внедрению

Базовая настройка

ОБЯЗАТЕЛЬНО. Загрузите скрипт эксперимента с помощью async и blocking="render". Атрибут async гарантирует, что скрипт не заблокирует синтаксический анализ HTML (браузер продолжит создавать DOM во время получения скрипта). Атрибут blocking="render" гарантирует, что браузер не будет ничего отрисовывать до выполнения скрипта.

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

Загрузка стилей для эксперимента

Если для эксперимента требуется таблица стилей варианта, используйте blocking="render" в элементе <link>. Таблицы стилей в <head> по умолчанию блокируют отрисовку, но динамически внедренные таблицы стилей или таблицы, добавленные с помощью скрипта, этого не делают. Используйте blocking="render", когда таблица стилей добавляется динамически или когда вы хотите явно указать цель.

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

Встроенный скрипт эксперимента

Если логика эксперимента достаточно проста, чтобы встроить ее в код, используйте встроенный скрипт модуля с blocking="render". Это полезно, когда логика эксперимента извлекает конфигурацию и применяет изменения 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>

Рекомендации

  • ОБЯЗАТЕЛЬНО. Скрипт эксперимента должен выполняться быстро. Медленный скрипт задерживает отрисовку. Установите бюджет производительности (например, время выполнения менее 100 мс) для скрипта, блокирующего отрисовку.
  • РЕКОМЕНДУЕТСЯ отделить логику эксперимента от скрипта, блокирующего отрисовку. Загрузите небольшой блокирующий рендеринг фрагмент, который применяет вариант, а затем загрузите более тяжелые скрипты отслеживания или аналитики отдельно с помощью async или defer (без blocking="render").
  • РЕКОМЕНДУЕТСЯ использовать атрибут данных в <html> или <body>, чтобы указать активный вариант, и селекторы CSS, привязанные к этому атрибуту, для оформления варианта. Это позволяет избежать прямого манипулирования DOM в скрипте, блокирующем отрисовку.
  • НЕ используйте blocking="render" в скриптах аналитики, отслеживания и других невидимых скриптах. Блокировать отрисовку должны только скрипты, которые меняют то, что пользователь видит при первой отрисовке.
  • НЕ используйте blocking="render" вместе с устаревшими фрагментами, скрывающими страницу. Они решают одну и ту же проблему, и использование обоих приводит к ненужным задержкам.

Резервные стратегии

Атрибут blocking поддерживается не во всех браузерах. В браузерах, которые не поддерживают этот атрибут, он игнорируется, и скрипт загружается в режиме работы по умолчанию (async в примерах выше), что может привести к мерцанию. Резервный вариант необходим, чтобы предотвратить мерцание в неподдерживаемых браузерах.

Резервный вариант: фрагмент, скрывающий страницу

Используйте легкий фрагмент, скрывающий страницу, в качестве резервного варианта, только если blocking="render" не поддерживается. Проверьте поддержку функции и пропустите резервный вариант в браузерах, которые обрабатывают ее нативно.

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

О чем ещё нужно помнить

  1. Влияние на производительность. blocking="render" снижает скорость первой отрисовки, чтобы обеспечить визуальную корректность. Отслеживайте показатели Largest Contentful Paint (LCP) и First Contentful Paint (FCP), чтобы убедиться, что скрипт эксперимента не вызывает чрезмерной задержки.
  2. Надежность сторонних скриптов. Если SDK эксперимента размещен в сторонней сети доставки контента, то сбой в ее работе может полностью заблокировать отрисовку. Браузер применяет собственную эвристику времени ожидания (которое может превышать четыре секунды), но разработчик не может управлять временем ожидания для blocking="render". Убедитесь, что сторонний поставщик гарантирует высокую доступность.
  3. Серверные альтернативы: Для страниц, где производительность критически важна, вместо клиентского тестирования можно использовать серверное A/B-тестирование (когда сервер сразу отрисовывает нужный вариант). Серверные подходы полностью устраняют мерцание без блокировки отрисовки. Используйте blocking="render" на стороне клиента, только если тестирование на стороне сервера невозможно.