問題說明
用戶端 A/B 測試工具的運作方式,是在瀏覽器開始建構網頁後,載入修改 DOM 的指令碼。如果沒有介入,使用者會先短暫看到原始內容,然後畫面會閃爍或閃現實驗變化版本。測試平台過去會使用「防網頁閃爍程式碼片段」來解決這個問題,也就是使用 opacity: 0 隱藏整個網頁,直到實驗指令碼完成或任意逾時 (通常為 4 秒) 為止。這種做法會犧牲漸進式算繪、允許意外點擊隱形內容,並產生不必要的繪製週期。
解決方法
blocking=render 屬性可讓放置在 <head> 中的 <script> 或 <link> 元素在擷取並執行資源前,封鎖轉譯 (但不會封鎖剖析)。這樣一來,實驗指令碼就會與樣式表一樣,預設為禁止轉譯,確保瀏覽器在套用實驗變化版本前,不會繪製網頁。不需調整不透明度、任意設定逾時時間,也不會出現閃爍。
導入策略
- 必要:將實驗指令碼放在文件
<head>中,並新增blocking="render"。 - 必要:請確認指令碼含有
type="module"(內嵌指令碼建議使用) 或async(外部指令碼建議使用,且值為src)。 - 建議做法:保持實驗指令碼精簡快速。由於轉譯作業會遭到封鎖,直到指令碼執行完畢為止,因此大型或緩慢的指令碼會直接延遲首次繪製。
- 請勿在不需於首次繪製前執行的指令碼上使用
blocking="render"。僅適用於輸出內容必須在初始算繪中顯示的指令碼。 - 請勿將
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>
載入實驗專屬樣式
如果實驗需要子類樣式表,請在 <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>
最佳做法
- 強制規定:實驗指令碼必須快速執行。指令碼速度緩慢會延遲所有轉譯作業。為會阻礙轉譯的指令碼設定效能預算 (例如執行時間不超過 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');
}
其他考量
- 效能影響:
blocking="render"會犧牲首次繪製速度,以確保視覺正確性。監控 Largest Contentful Paint (LCP) 和 First Contentful Paint (FCP),確保實驗指令碼不會造成過度延遲。 - 第三方指令碼可靠性:如果實驗 SDK 託管於第三方 CDN,CDN 服務中斷可能會導致網頁完全無法顯示。瀏覽器會套用自己的逾時經驗法則 (可能超過 4 秒),但開發人員無法控制
blocking="render"的逾時時間。確保第三方供應商提供可靠的運作時間保證。 - 伺服器端替代方案:對於效能至關重要的網頁,建議採用伺服器端 A/B 測試 (由伺服器直接顯示正確的變化版本),而非用戶端測試。伺服器端方法可完全消除閃爍,且不會造成任何轉譯阻斷成本。只有在無法進行伺服器端測試時,才使用用戶端
blocking="render"。