Skip to content

Prevent flicker

Flicker is the moment when a visitor sees your original page before a variation replaces it. Grona prevents it with a small synchronous script that hides the page until the changes are in place. This page explains how that works, what it costs, and which loading choices defeat it.

The second line of the snippet loads antiflicker.js synchronously, so the browser runs it before it paints anything below it. The script:

  1. Checks the visitor’s user agent. For bots it does nothing.
  2. Adds a <style> element to <head> that sets visibility: hidden on <body> and paints the background colour from data-grona-af-bg.
  3. Starts a 5-second safety timer.

Meanwhile main.js downloads in the background. When it runs, it reveals the page as soon as it knows there is nothing to wait for:

  • straight away when no campaign matches the page;
  • after the variation’s changes are applied, when the visitor is in a campaign (this waits for the DOM to be ready);
  • straight away for bots.

If main.js has not revealed the page within 5 seconds, the anti-flicker script reveals it anyway and sends a timeout report to Grona. The page is not kept hidden for longer than that, even if main.js fails to load. The 5 seconds start once antiflicker.js has run; while it downloads, the browser waits for it as for any synchronous script, so a slow connection to cdn.grona.ai delays the whole page.

When main.js finishes after the timeout, the changes still apply, but now in view of the visitor. This is a late apply: the visitor sees the original first and then the variation. Slow networks and slow pages make it more likely.

Grona sends two timing reports to api.grona.ai/track/antiflicker. Each carries your workspace id, the page URL, the browser’s user agent and a duration in milliseconds. Neither is sent for bots.

Report Sent by When Visitor and visit ids
loaded main.js When main.js starts on a page view, whether or not a campaign runs on the page. Not sent on preview links. Under Wait for consent, sent only when the visitor has consented. Never included
timeout antiflicker.js When the 5-second timer has to reveal the page. Sent whatever your consent setting is. Included only when main.js has already set them, which it never does before consent under Wait for consent

A page that main.js reveals in time sends only loaded. See Consent and storage for what else runs before consent.

data-grona-af-bg sets the colour visitors see while the page is hidden. The dashboard snippet uses #ffffff. Set it to your page’s own background colour so the wait looks like a page that has not loaded yet. If you remove the attribute, the background is transparent.

<script src="https://cdn.grona.ai/workspaces/YOUR_WORKSPACE_ID/antiflicker.js" data-grona-af-bg="#f7f5f0"></script>

The page is hidden on every page that carries the snippet, including pages with no campaign, until main.js has loaded and run. On pages with a campaign it stays hidden until the DOM is ready and the changes are applied. Expect a short blank on every page view, at most 5 seconds after the anti-flicker script runs. Performance explains how to keep it short.

You can leave the anti-flicker line out of the snippet (the dashboard’s Include Antiflicker option). Then nothing ever hides your page and no blocking request sits in your <head>. main.js still sends its loaded timing report. The cost is that visitors in a campaign can see the original content before the variation replaces it.

Why tag managers and async loaders cause flicker

Section titled “Why tag managers and async loaders cause flicker”

The anti-flicker script only works when the browser runs it before the first paint, which is why it is a plain synchronous tag at the top of <head>. Anything that adds it later cannot hide what is already on screen:

  • Google Tag Manager and other tag managers inject tags after their own container loads. See Google Tag Manager.
  • Framework script loaders, such as Next.js next/script, load scripts asynchronously.
  • “Delay JavaScript” and script-combining options in cache and optimisation plugins hold back or reorder the tag. Exclude both Grona scripts from them.

The page is hidden only on the first page load. When a single-page app changes route without a reload, Grona re-runs its campaigns without hiding anything, so the new route’s original content can show for a moment before the changes apply. See Single-page applications.

Lighthouse, PageSpeed Insights and GTmetrix are treated as bots. They see your untouched page with nothing hidden and no variation applied, so their reports show neither the flicker nor the hiding time. Use real-user monitoring to measure what visitors experience.