Server-side vs client-side
The choice of where the experiment decision happens - browser, server, or edge - sets the ceiling on what’s possible. Most CRO programmes start client-side because that’s what the marketing-friendly tools sell. They keep running client-side long past the point where the constraints start eating into results.
The three models
Section titled “The three models”Client-side
Section titled “Client-side”A script loads in the browser, asks the experiment platform which variant the user is in, then swaps the DOM. This is what Optimizely Web, VWO, and most tag-manager-installed tools do.
window.experiment.evaluate('hero_test', user) .then(variant => { if (variant === 'treatment') { document.querySelector('.hero h1').textContent = 'New headline' } })Pros: easy to install, marketing team can ship tests without engineering, works on platforms where you don’t control the server (Shopify themes, most CMS templates).
Cons: flicker, the variant decision is visible in network requests and devtools, third-party script in the critical path, randomisation integrity weaker than it looks.
Server-side
Section titled “Server-side”The server resolves the variant before sending HTML. Whatever rendered is what the user sees, with no JS swap.
// In the route handler, before renderconst variant = experimentSDK.evaluate('hero_test', user.id)const headline = variant === 'treatment' ? 'New headline' : 'Old headline'return render('home', { headline })Pros: no flicker, no client-side leakage, the SDK lives in your backend so the experiment platform is just a config service.
Cons: requires backend access, the test goes through your release process unless you architect for runtime config, harder for marketing to ship without engineering.
The variant is decided in a CDN worker (Cloudflare Workers, Vercel Edge Middleware, Akamai EdgeWorkers), often by rewriting cookies or routing the user to a cached HTML variant.
export default { async fetch(request) { const cookie = request.headers.get('cookie') || '' const variant = decideVariant(cookie, 'hero_test') return fetch(request, { cf: { cacheKey: `hero_${variant}` } }) }}Pros: server-side validity, no origin work, very fast. Good fit for static or heavily-cached sites.
Cons: only works if variants can be pre-rendered or cached, debugging edge code is painful, the worker becomes a critical dependency.
Which to pick
Section titled “Which to pick”The honest framing isn’t “server-side is better”. It’s “what’s the test changing, and where does that change live?”.
- Change is a string or class swap. Both work. Client-side is fine if you accept the flicker, server-side is fine if you have backend control.
- Change affects what gets loaded (a recommendations module fetched from a different API). Server-side. Client-side means the request and the swap both happen after first paint and the user feels it.
- Change is in the cart, account area, or anywhere with authenticated state. Server-side. Flicker on a checkout step is unforgivable.
- Change is static content on a heavily cached marketing page. Edge. Origin doesn’t see the load and the variant ships at CDN speed.
- You’re on a managed platform with no server access (Shopify themes, most SaaS site builders). Client-side, with mitigations. The Shopify constraints note covers the workarounds.
The mix problem
Section titled “The mix problem”Mixing models on the same property is where teams quietly bleed. Server-side tests on the PDP, client-side tests on the homepage, edge experiments on the blog - each platform thinks it owns variant assignment, and they don’t agree.
Two things keep this manageable:
- One source of truth for assignment. Whichever layer made the decision first writes a cookie. Downstream layers read the cookie instead of re-deciding. The user is in one variant, full stop.
- A flag service the others call. Feature flags treated as configuration mean client, server, and edge all hit the same endpoint and get the same answer.
The architectural goal is the same regardless of which model you start with: variant assignment is a function of user, not of which layer happened to ask first.
Where it falls down
Section titled “Where it falls down”- Letting marketing tools default to client-side everywhere. The path of least resistance becomes the architecture. Audit annually - tests that should be server-side often aren’t.
- Server-side tests that quietly skip QA. Because they ship through engineering, they get the same code review as a feature. They also need experiment-specific checks (sample ratio, instrumentation parity) that engineering review won’t catch.
- Edge tests that share cache keys across variants. The first user’s variant gets cached and served to everyone. Always include the variant in the cache key.