> ## Documentation Index
> Fetch the complete documentation index at: https://docs.trevosdk.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Flicker on first paint

> Why a first-time visitor briefly sees the control version, and the three ways to stop it.

A first-time visitor loads your page, sees the current version for a moment, and then it
switches to the variant. Returning visitors don't see it.

That's expected behaviour, not a bug — and it has three fixes depending on your stack.

## Why it happens

`getVariant()` is synchronous and needs experiment config to answer correctly. Where that
config comes from differs by visitor:

* **Returning visitor** — config is in local storage from a previous visit, read
  synchronously before first paint. Correct immediately, no flicker.
* **First-time visitor** — no cache. The SDK returns `control` while the first config fetch
  is in flight, then re-renders with the real assignment when it lands.

Nothing is hidden — and nothing is miscounted, because a pre-config `control` read records
no exposure at all. `@trevosdk/react`'s `useExperiment` re-reads once config lands and
reports the exposure on the settled variant. If you call `trevo.getVariant()` directly in
run-once code, re-read after `await trevo.ready()` (or subscribe with `trevo.onChange()`)
or the visitor is never counted.

The SDK tells you when that happens: it warns in the console and reports a
`read_before_ready` error for the experiment, and the experiment's page in the dashboard
shows **Some visitors aren't being counted** with how many were missed in the last 7 days:
visitors whose page read early and never recorded an exposure. The warning clears once a day
passes without a new one, so when it is gone your fix is working. Those visitors are mostly
first-time ones, so until the read waits for readiness the results lean toward returning visitors.

Config is cached for 24 hours, so this affects genuinely new visitors and people who cleared
storage — not your regular traffic.

## Fix 1 — Resolve on the server (best)

If you're on Next.js, the server can resolve variants before any HTML is sent, so the first
paint is already correct for everyone. Nothing is hidden and there's no timeout to tune.

```ts theme={null}
// proxy.ts — Next.js 16+
export { proxy } from '@trevosdk/nextjs';

export const config = {
  matcher: ['/((?!api(?:/|$)|_next/static|_next/image|favicon\\.ico|sitemap\\.xml|robots\\.txt|.*\\.(?:avif|css|eot|gif|ico|jpe?g|js|json|map|mjs|mp3|mp4|otf|pdf|png|svg|ttf|txt|wav|webm|webp|woff2?|xml|zip)$).*)'],
};
```

On Next.js 14 or 15, use `middleware.ts` and export `middleware` instead. Put the one
request-hook file beside the app's `app`/`pages` directory (at the app root or under `src`),
and preserve custom `pageExtensions`. The matcher must be an inline literal — Next extracts
it by static analysis.

```tsx theme={null}
const bootstrap = await getTrevoBootstrap();
<TrevoProvider apiKey={apiKey} bootstrap={bootstrap}>{children}</TrevoProvider>
```

Full setup in [Next.js](/install/nextjs). Other server frameworks can do the same via
`resolveExperiments()` from `@trevosdk/nextjs/server`.

## Fix 2 — Anti-flicker snippet

If you can't resolve server-side, hide the page until variants are ready. Inline this in
`<head>`, before anything renders:

```tsx theme={null}
import { ANTI_FLICKER_SNIPPET } from '@trevosdk/browser';

// it's a string — inject it as the script's contents, in the document head:
<script dangerouslySetInnerHTML={{ __html: ANTI_FLICKER_SNIPPET }} />
```

In plain HTML, paste its contents into a `<script>` tag directly.

It sets `body { opacity: 0 }` and reveals automatically once config arrives — or after a
**1 second safety timeout**, whichever comes first.

That timeout is the important part. If an ad blocker, a CSP rule, or a network failure stops
the SDK loading, the page reveals anyway rather than staying blank. Raise it if your users are
on slow networks, but understand the trade:

```html theme={null}
<script>window.__trevoConfig = { timeout: 2000 };</script>
```

Set it in a script that runs **before** the snippet — it's read once, at snippet
execution — and values above 10 s are clamped to 10 s.

You're choosing between a brief flash and a brief blank page. Neither is free — the blank page
is usually less jarring, but it's worse if it goes wrong.

For full control, initialise with `manualReveal: true` and call `trevo.reveal()` yourself once
you've committed the variant.

## Fix 3 — Design around it

Often the cheapest answer. A first-paint swap is only visible if the tested element is visible
on first paint.

* Test things **below the fold**, or behind an interaction — a modal, a second step, a menu
* Gate rendering on readiness where the surface can wait:

```ts theme={null}
if (!trevo.isReady()) return <Skeleton />;
```

* Or `await trevo.ready()` before rendering the section under test

If your experiment is on a hero CTA for a landing page whose whole audience is first-time
visitors, that's exactly the case for fix 1 or 2.

## Which to choose

| Situation | Use |
| - | - |
| Next.js | Server bootstrap |
| Other SSR framework | `resolveExperiments()` from `/server` |
| Client-only app, element above the fold | Anti-flicker snippet |
| Element below the fold or behind interaction | Nothing — it isn't visible |
| Mostly returning visitors | Nothing — they read from cache |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.