깜박임 없는 클라이언트 측 A/B 테스팅

웹 기능: blocking="render"

문제

클라이언트 측 A/B 테스트 도구는 브라우저가 이미 페이지를 구성하기 시작한 후 DOM을 수정하는 스크립트를 로드하는 방식으로 작동합니다. 개입이 없으면 사용자는 실험 대안으로 깜박이거나 번쩍이기 전에 원본 콘텐츠를 잠시 보게 됩니다. 테스트 플랫폼은 지금까지 실험 스크립트가 완료되거나 임의의 제한 시간 (일반적으로 4초)이 경과할 때까지 opacity: 0를 사용하여 전체 페이지를 숨기는 '깜박임 방지 스니펫'을 사용하여 이 문제를 해결해 왔습니다. 이 접근 방식은 점진적 렌더링을 희생하고, 보이지 않는 콘텐츠에 대한 의도하지 않은 클릭을 허용하며, 불필요한 페인트 주기를 도입합니다.

해결 방법

blocking=render 속성을 사용하면 <head>에 배치된 <script> 또는 <link> 요소가 리소스를 가져와 실행할 때까지 렌더링을 차단할 수 있지만 파싱은 차단할 수 없습니다. 이렇게 하면 실험 스크립트가 스타일시트와 동일한 렌더링 차단 동작을 기본적으로 갖게 되므로 실험 대안이 적용될 때까지 브라우저가 페이지를 그리지 않습니다. 불투명도 해킹, 임의의 제한 시간, 깜박임이 없습니다.

구현 전략

  1. 필수: 실험 스크립트를 문서 <head>에 배치하고 blocking="render"를 추가합니다.
  2. 필수: 스크립트에 type="module" (인라인 스크립트에 권장) 또는 async(src 값이 있는 외부 스크립트에 권장)가 있는지 확인합니다.
  3. 실험 스크립트를 작고 빠르게 유지하세요. 스크립트가 실행될 때까지 렌더링이 차단되므로 크거나 느린 스크립트는 첫 번째 페인트를 직접 지연시킵니다.
  4. 첫 번째 페인트 전에 실행할 필요가 없는 스크립트에는 blocking="render"를 사용하지 마세요. 초기 렌더링에서 출력이 표시되어야 하는 스크립트에만 사용됩니다.
  5. <head> 외부의 스크립트에는 blocking="render"를 적용하지 마세요. <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>

실험별 스타일 로드 중

실험에 대안 스타일시트가 필요한 경우 <link> 요소에 blocking="render"를 사용합니다. <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>

권장사항

  • 필수: 실험 스크립트는 빠르게 실행되어야 합니다(MUST). 느린 스크립트는 모든 렌더링을 지연시킵니다. 렌더링 차단 스크립트에 대한 성능 예산 (예: 실행 시간 100ms 미만)을 설정합니다.
  • 권장사항: 렌더링 차단 스크립트에서 무거운 실험 로직을 분할합니다. 대안을 적용하는 작은 렌더링 차단 스텁을 로드한 다음 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"는 시각적 정확성을 위해 첫 페인트 속도를 희생합니다. 최대 콘텐츠 페인트 (LCP) 및 첫 콘텐츠 페인트 (FCP)를 모니터링하여 실험 스크립트가 과도한 지연을 추가하지 않는지 확인합니다.
  2. 서드 파티 스크립트 안정성: 실험 SDK가 서드 파티 CDN에서 호스팅되는 경우 CDN 중단으로 인해 렌더링이 완전히 차단될 수 있습니다. 브라우저는 자체 제한 시간 휴리스틱 (4초보다 길 수 있음)을 적용하지만 blocking="render"에는 개발자가 제어하는 제한 시간이 없습니다. 서드 파티 제공업체가 강력한 업타임 보장을 제공하는지 확인합니다.
  3. 서버 측 대안: 성능이 중요한 페이지의 경우 클라이언트 측 테스트 대신 서버 측 A/B 테스트 (서버가 올바른 대안을 직접 렌더링함)를 고려하세요. 서버 측 접근 방식은 렌더링 차단 비용 없이 깜박임을 완전히 제거합니다. 서버 측 테스트가 불가능한 경우에만 클라이언트 측 blocking="render"를 사용합니다.