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.
How the anti-flicker script works
Section titled “How the anti-flicker script works”The second line of the snippet loads antiflicker.js synchronously, so the browser runs it before it paints anything below it. The script:
- Checks the visitor’s user agent. For bots it does nothing.
- Adds a
<style>element to<head>that setsvisibility: hiddenon<body>and paints the background colour fromdata-grona-af-bg. - 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.
The 5-second cap
Section titled “The 5-second cap”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.
Timing reports
Section titled “Timing reports”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.
Choose the background colour
Section titled “Choose the background colour”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>What it costs
Section titled “What it costs”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.
Run without it
Section titled “Run without it”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.
Route changes in single-page apps
Section titled “Route changes in single-page apps”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.
Lab tools do not show flicker or its cost
Section titled “Lab tools do not show flicker or its cost”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.