The Flicker Problem: Why Client-Side A/B Testing Breaks User Experience

If visitors see the original page for 200 milliseconds before the variant loads, your test results are measuring something unintended — and your users notice the visual glitch.

Flicker — also called Flash of Original Content — is one of the oldest and most persistent problems in client-side A/B testing. It occurs when the browser renders the original page before the testing script has had time to apply the variant. The visitor sees the control state for a brief moment, then the page visually snaps to the variant. In many cases the snap is imperceptible. In others, it is jarring enough to alter behavior.

This article explains the mechanics of why flicker occurs, the different implementation patterns that cause it to varying degrees, what its effect is on experiment validity, and the engineering approaches that reliably eliminate it. Most of what is written here applies to any JavaScript-based testing tool, though the specific mitigation strategies are informed by how Webyn approaches the problem.

browser loading sequence showing page flash before variant applies

The Browser Rendering Pipeline and Where Testing Scripts Fit In

When a browser receives an HTML document, it begins constructing the DOM while simultaneously downloading linked resources. JavaScript files with a standard script tag block HTML parsing until the script has been fetched and executed. This behavior is why testing scripts have traditionally been placed in the document head — they need to run before any visible content is rendered, so they can apply variant changes before the page appears to the user.

The problem is that modern browsers and performance guidelines push in the opposite direction. The Core Web Vitals framework penalizes render-blocking resources. CDN-hosted scripts add network round-trip time before execution can begin. Content Security Policies can delay or prevent inline script execution. Each constraint that improves general page performance makes it harder to run testing code before initial paint.

The result is a timing race. The testing script needs to run before paint. Everything in the modern web stack works to delay script execution. When the page wins the race, flicker occurs.

Four Implementation Patterns and Their Flicker Profiles

The degree of flicker risk depends heavily on how the testing script is loaded. There are four common patterns, each with a different risk profile.

The first pattern is a synchronous script tag in the document head, pointing to a CDN-hosted file. This blocks parsing until the script is downloaded and executed. If the CDN responds in under 50ms, flicker is rare. If the CDN is slow — due to geographic distance, cache miss, or network conditions — parsing blocks for 200ms or more, but the content below is not rendered yet, so there is no visible flicker. However, this pattern is what causes Largest Contentful Paint scores to suffer, which conflicts with performance optimization goals.

The second pattern is an asynchronous script tag. Async loading does not block parsing, which means the browser can begin rendering the page while the script downloads. If the script arrives after first paint — which it often does — variant changes are applied after the user has already seen the control. This is the most flicker-prone pattern and should not be used for testing scripts that modify above-the-fold content.

The third pattern uses an anti-flicker snippet: a short inline script that hides the body element until the testing script has executed. The snippet runs synchronously in the head, sets opacity to zero or inserts a style rule hiding the body, and registers a timeout after which the page becomes visible regardless of whether the testing script has executed. When the testing script runs, it removes the hiding style and applies the variant.

Anti-flicker snippets are the most common mitigation in commercial testing tools. Their effectiveness depends on two things: the timeout value and the execution speed of the testing script. If the timeout is too short, flicker still occurs. If it is too long, the page loads blank for an uncomfortable period. Most tools default to 1500ms, which is long enough to cause measurable performance degradation and user frustration.

The fourth pattern is server-side rendering. No client-side swap occurs because the correct variant is included in the HTML response. Flicker is impossible by design. The trade-off is engineering complexity and the loss of visual editing capabilities.

How Flicker Contaminates Experiment Results

The intuitive concern about flicker is user experience: the visual snap looks broken. The less obvious concern is measurement validity. When a visitor sees the control state before the variant is applied, their behavioral response is to a hybrid experience, not to the variant as designed. If the control state includes a headline that the variant replaces, and the visitor reads that headline before the swap occurs, their engagement data reflects an experiment condition that was never intended.

This contamination is asymmetric. It always affects the variant, never the control, because the control is the original page. Every variant visitor has been exposed, however briefly, to the control condition. This produces a systematic bias toward the null hypothesis. Variants that would be significantly better in a clean test may fail to reach significance when flicker noise dilutes the effect. You end up running longer experiments to overcome noise that should not exist.

Measuring flicker contamination in practice is difficult because it requires knowing which users experienced flicker and for how long. Some testing tools log this. Most do not. The safest assumption for any client-side test with an asynchronous loading pattern is that a meaningful fraction of variant sessions were contaminated.

The Timeout Problem in Anti-Flicker Implementations

The timeout in anti-flicker snippets deserves more attention than it typically receives. The timeout exists as a failsafe: if the testing script never loads — due to a network error, an ad blocker, or a content security policy — the page should still become visible. Without a timeout, a single CDN outage would blank every page on your site for every visitor for the duration of the outage.

The timeout value, however, is also the maximum duration during which your page appears blank on every page load. If your testing script consistently takes 300ms to execute, a 1500ms timeout means 1200ms of unnecessary blank time — because the timeout never fires, but the blank period still lasts until the script finishes. Users experience a blank page for the full script execution time on every page load, regardless of whether they are in an experiment.

The correct timeout value is the 99th percentile execution time of your testing script, plus a small buffer. For most implementations, this is between 200 and 400 milliseconds. Setting the timeout at 1500ms because it is the default in your tool's documentation is a common mistake with a real performance cost.

Measuring your actual script execution time requires instrumentation. Add a performance.mark at the start and end of your testing script, capture the difference, and send it to your analytics platform. Run this measurement for a week, compute percentiles, and set your timeout accordingly. This simple exercise typically reduces anti-flicker blanking time by 60 to 80 percent in implementations that have never been tuned.

Webyn's Approach: Predictive Pre-Rendering

Webyn handles flicker through a combination of server-side pre-computation and a lightweight inline assignment cache. When a visitor is assigned to a variant for the first time, the assignment is computed server-side and included in the first-party cookie set by the server on the initial response. On subsequent page loads, the inline snippet — which is under two kilobytes of inlined JavaScript — reads the assignment from the cookie synchronously, before any rendering occurs, and sets the variant class on the body element before the first paint.

The inline snippet has no network dependency. It reads a cookie value, performs a simple lookup, and adds a CSS class. On a modern browser, this takes under two milliseconds. There is no timeout because there is no asynchronous operation. The variant is applied before the browser renders any visible content.

For first-time visitors where no cookie exists, Webyn uses a server-side decision that is included in the HTML response via an edge middleware layer. The variant is rendered server-side, the assignment cookie is set in the response headers, and the client receives the correct variant state as part of the initial HTML. No JavaScript swap occurs on first visit either.

What to Measure in Your Current Implementation

If you are running experiments with an existing testing tool and want to assess your flicker exposure, the following measurements provide a reasonable baseline. Use browser developer tools with CPU throttling applied — 4x slowdown is a reasonable simulation of mid-range mobile devices — and record a screen capture of a controlled page load while assigned to a non-control variant. Play back the recording frame by frame at the point of initial paint. If you can see the control state at any frame, flicker is present.

A more systematic measurement uses the PerformanceObserver API to record the timestamps of layout shifts. Flash of Original Content produces a measurable layout shift that correlates with the Cumulative Layout Shift metric. If your variant-assigned pages have significantly higher CLS scores than control-assigned pages, flicker is a likely cause.

Core Web Vitals data segmented by experiment assignment is the highest-fidelity signal. If the LCP and CLS scores for users in variant groups are consistently worse than for users in the control group, the testing implementation itself is introducing a performance penalty. This is a particularly important measurement for e-commerce sites where page speed correlates directly with conversion rate — your testing tool may be artificially depressing the variants it is supposed to be measuring.

The Server-Side Transition Path

For teams dealing with persistent flicker that cannot be resolved through snippet optimization, the medium-term solution is a partial or full migration to server-side testing. Full server-side testing requires either a back-end deployment change or an edge computing layer that can intercept requests and inject variant parameters before the response reaches the browser.

A practical intermediate step is edge-based assignment. Modern CDN providers including Cloudflare Workers, Fastly Compute, and Vercel Edge Functions can run JavaScript at the edge with latency under five milliseconds. Assignment logic runs at the edge, the correct variant identifier is passed to the origin as a request header, and the origin includes the appropriate variant in the rendered HTML. The entire decision happens before any bytes are sent to the browser, eliminating the timing race entirely.

The engineering investment is real, but for high-traffic sites where flicker is affecting millions of sessions per month, the payoff in experiment data quality justifies the migration effort. Testing on clean data consistently yields faster result validity and higher-confidence decisions than testing on noisy data from a flicker-contaminated implementation.

Test without flicker

Webyn's predictive pre-rendering approach eliminates Flash of Original Content by design. Your variants load clean on every page view, from the first visit onward.

See How It Works
← Back to Blog