Next.js A/B testing
Next.js A/B testing: server-side flags or a client-side script
Next.js A/B testing comes in two shapes: server-side experiments that decide the variant before the page renders, usually with Vercel's Flags SDK, and client-side tools that change the page in the browser. This guide explains both, then shows how Otter installs in the App Router, follows client-side navigation, and when a server-side setup is the better choice.
Install location
app/layout.tsx (App Router) or pages/_document.tsx (Pages Router)
One shared installation powers every experiment, conversion goal, and report on this site.
- 01Copy the Next.js snippet from your Otter project's install page. It adds the anti-flicker style, a reveal failsafe, and the SDK through next/script with strategy="afterInteractive" and your key in data-optimo-key.
- 02Paste it into the <head> of app/layout.tsx, or into pages/_document.tsx if you use the Pages Router, then deploy.
- 03Verify the installation from Otter and create URL rules for the routes under test.
- 04Send product actions as goals with window.optimo.track('event_name') from your event handlers, and purchases with window.optimo.revenue().
Ideas
What to test on Next.js
Start with the changes closest to the money. These are the ones teams on this platform run first.
The guide
Next.js A/B testing, explained
Next.js A/B testing: server-side or client-side?
Next.js has no built-in A/B testing product, so experiments run one of two ways. Server-side, your code decides the variant before rendering and sends only that version. Vercel's Flags SDK is the common route: a framework-native library for Next.js and SvelteKit that works with Vercel Flags or with Marketplace providers such as Statsig, PostHog, GrowthBook and LaunchDarkly. Client-side, a script loads in the browser, assigns the visitor and changes the page, which is how Otter and most visual testing tools work.
The Flags SDK's authors argue that flags belong on the server, because client-side flags cause layout shift: the page shows one version or a spinner and then swaps. Server-side testing avoids that, but every test costs engineering time and puts variants in your codebase. Client-side testing lets marketers change copy and layout without a deploy, at the cost of a short wait while the script decides. Many teams use both: flags for product features and a visual tool for marketing pages. We make Otter, the client-side tool described below, so weigh our view of the trade-offs accordingly.
How server-side tests work with Vercel's Flags SDK
The Flags SDK's precompute pattern keeps pages static. You evaluate flags in Proxy, which replaced the middleware file convention (Next.js has deprecated middleware.ts and renamed it proxy.ts), encode the result into a signed code using a FLAGS_SECRET, and rewrite the request to a route such as app/[code]/page.tsx. Each combination of flag values becomes a statically generated page, served through ISR or generated at build time.
The trade-offs are documented. Every flag multiplies the number of static permutations, so many flags or many values mean longer builds or lower cache hit rates, and ISR URLs must stay under 1,024 characters. Proxy defaults to the Node.js runtime. During React Server Components requests, Next.js strips internal Flight headers in Proxy; NextResponse.rewrite() carries the needed headers through, while custom fetch-based rewrites may not. Cost depends on the flag provider you choose, so check each provider's own pricing.
Choose this route for experiments on logged-in product flows, pricing logic, feature rollouts and anything that must render with no hidden period. Otter has no server-side SDK and no feature flags, so it doesn't replace this setup. For a closer look at that difference, read Otter vs LaunchDarkly.
How Next.js A/B testing works with Otter
Otter is a client-side tool. Its Next.js snippet goes in the <head> of app/layout.tsx, or pages/_document.tsx with the Pages Router. It adds three things: an inline style that hides the page, a reveal script that removes the style after 3 seconds as a failsafe, and the SDK loaded with next/script and strategy="afterInteractive". The SDK, about 32 KB gzipped, asks Otter for the visitor's test settings, applies the assigned variant and reveals the page. A returning visitor whose test can be served from cache sees it applied immediately.
Tests are then built outside your codebase. The visual editor opens your deployed site in a popup and changes text, HTML, attributes, styles and inserted content, so a marketer can test a pricing page headline without a pull request. Split URL tests send visitors to a second route, such as /pricing-b, through a browser redirect. Per-variant custom CSS and JavaScript cover what the editor can't.
For goals, call window.optimo.track('signup_completed') from your handlers; before the SDK has loaded, push calls onto window.optimoq and they replay once it boots. Revenue goes through window.optimo.revenue() with a value, currency and order ID. Otter can also send impressions and conversions to GA4 and the GTM dataLayer, or use GA4 events you already send as goals. See how Otter works for the full flow.
Client-side navigation in the App Router
Next.js changes routes without reloading the page, and a script loaded once in the root layout runs on the first page load only. Otter handles route changes itself. It detects Next.js from its _next/static scripts, patches history.pushState and replaceState, listens for popstate and hashchange, and polls the URL every 100 ms as a fallback. On each URL change it fetches settings again and re-evaluates which tests apply, and pageview goals re-check against the new URL.
Components that render after data loads need the right trigger. Use Wait for element to apply a test when a CSS selector appears, or DOM change to re-apply changes when React re-renders part of the page. For full control, use the Manual trigger and call window.optimo.execute(testId) from your component when it's ready. Otter also retries changes for 2 seconds on late-rendered elements, and re-applies "all instances" changes when matching elements appear.
Performance, flicker and Core Web Vitals
Otter hides the page until it has the visitor's test settings, with a 3-second failsafe, and it requests settings on every page that loads the snippet, including routes with no test. That stops visitors from seeing the original swap to a variant, but it adds a wait before the page becomes visible that depends on the round trip to Otter. Split URL tests always wait for that request, because redirect decisions are never served from the local cache. Measure your Largest Contentful Paint before and after installing.
Three ways to limit the cost. If only your marketing routes run tests, consider loading the snippet in that route group's layout so other routes don't wait on it. Keep variant routes fast, because a split URL test adds a navigation. And if a test must render with no hidden period at all, run it server-side with the Flags SDK. Strict Content Security Policies need attention too: allow Otter's domain for scripts and network requests, allow the inline anti-flicker style and reveal script with a nonce or hash, and note that per-variant custom JavaScript runs through new Function, so it needs 'unsafe-eval'.
What to test in a Next.js app
Next.js runs many SaaS marketing sites and product front ends, and the best first tests sit where visitors decide to sign up: the homepage headline and primary call to action, the pricing page's plan order and highlighted plan, and the first screen of signup. Because Otter follows route changes, a multi-step signup that never reloads can still be tested step by step, with a pageview goal on the final route or a custom event when the account is created.
Target tests to the audience that matters. Otter can split by UTM campaign, traffic source, device, country, new or returning visitors, cookies, URL parameters and your own visitor properties sent with window.optimo.identify(), such as plan or account type. Results can be broken down by the same dimensions afterwards. Pick a primary goal before launch and size the test with the sample size calculator.
Next.js A/B testing and SEO
Google's testing guidance says changing content with JavaScript is a valid way to test without new URLs, that redirects to a variant URL should be temporary (302) or JavaScript, and that alternate URLs should carry rel="canonical" pointing to the original. In the App Router, set a variant route's canonical with the Metadata API's alternates.canonical field. Don't cloak, and end tests once you have an answer.
Otter doesn't assign known bots to a variant, so crawlers see your original render. Server-side tests need the same care: if Proxy assigns variants by cookie, Google notes that Googlebot generally doesn't keep cookies, so it sees the version served to browsers without them, which should be your control.
Questions
Next.js A/B testing questions
How do I A/B test in Next.js?
Does Otter work with the Next.js App Router?
Does Otter have a server-side SDK for Next.js?
Will A/B testing cause flicker in Next.js?
How do I track conversions in a Next.js app?
Does A/B testing affect SEO in Next.js?
Can I use Otter with a strict Content Security Policy?
Keep reading
Related guides
Built for Next.js
Run your first test this afternoon
One snippet, no build step, and results you can act on within days.
- 14-day free trial
- No credit card required
- Cancel anytime