ScaledByDesign/Insights
ServicesPricingAboutContact
Book a Call
Scaled By Design

Fractional CTO + execution partner for revenue-critical systems.

Company

  • About
  • Services
  • Contact

Resources

  • Insights
  • Pricing
  • FAQ

Legal

  • Privacy Policy
  • Terms of Service

© 2026 ScaledByDesign. All rights reserved.

contact@scaledbydesign.com

On This Page

The Flicker ProblemHow Client-Side Testing WorksThe Performance TaxHow Server-Side Testing WorksServer-Side ImplementationThe Decision FrameworkEdge-Side Testing (The Modern Approach)
  1. Insights
  2. Split Testing & Tracking
  3. A/B Testing: Server-Side vs. Client-Side — The Technical Trade-offs

A/B Testing: Server-Side vs. Client-Side — The Technical Trade-offs

May 1, 2026·ScaledByDesign·
ab-testingexperimentationperformanceconversion-optimization

The Flicker Problem

You've seen it: a page loads, shows the original version for 200ms, then snaps to the variant. That's the "flash of original content" (FOOC) caused by client-side A/B testing. Your analytics say the test is neutral, but the flicker itself is hurting conversion by creating a jarring user experience.

Client-side testing tools (Google Optimize, VWO, Optimizely's client SDK) work by loading a JavaScript snippet that modifies the DOM after the page renders. This creates three problems: flicker, performance degradation, and variant leaking to bots.

How Client-Side Testing Works

1. Browser requests page
2. Server sends original HTML
3. Browser renders original page (user sees it briefly)
4. A/B test script loads (blocking or async)
5. Script determines variant assignment
6. Script modifies DOM to show variant
7. User sees variant (after flicker)

Timeline:
  |----page load----|--script load--|--DOM modify--|
  0ms             800ms          1200ms          1400ms
                    ↑                              ↑
              User sees original              User sees variant
              (flicker window: 600ms)

The Performance Tax

Every client-side testing tool adds JavaScript that blocks rendering:

Typical performance impact:
  Google Optimize:    80-120ms added to LCP
  VWO:              100-200ms added to LCP
  Optimizely Web:   150-300ms added to LCP
  AB Tasty:         100-250ms added to LCP

These numbers compound with:
  → Number of active experiments
  → Complexity of DOM modifications
  → User's device/network speed
  → Other third-party scripts competing for main thread

For an e-commerce site where every 100ms costs 0.7% in conversion, a 200ms testing overhead is silently costing you money on every experiment.

How Server-Side Testing Works

1. Browser requests page
2. Server determines variant assignment (cookie/header check)
3. Server renders the correct variant HTML
4. Browser receives and renders the final version
5. User sees the variant immediately (no flicker)

Timeline:
  |--------page load with correct variant--------|
  0ms                                          800ms
                                                 ↑
                                           User sees variant
                                           (zero flicker)

Server-Side Implementation

// Next.js middleware for server-side A/B testing
import { NextRequest, NextResponse } from "next/server";
 
export function middleware(request: NextRequest) {
  // Check for existing variant assignment
  let variant = request.cookies.get("exp_homepage_v2")?.value;
 
  if (!variant) {
    // Assign variant based on consistent hashing
    const userId = request.cookies.get("user_id")?.value || crypto.randomUUID();
    variant = hashToVariant(userId, "homepage_v2", [
      { id: "control", weight: 50 },
      { id: "variant_a", weight: 50 },
    ]);
 
    const response = NextResponse.next();
    response.cookies.set("exp_homepage_v2", variant, { maxAge: 60 * 60 * 24 * 30 });
    response.cookies.set("user_id", userId, { maxAge: 60 * 60 * 24 * 365 });
    
    // Pass variant to the page via header
    response.headers.set("x-experiment-variant", variant);
    return response;
  }
 
  const response = NextResponse.next();
  response.headers.set("x-experiment-variant", variant);
  return response;
}
 
// Consistent hashing ensures same user always sees same variant
function hashToVariant(userId: string, experimentId: string, variants: Variant[]): string {
  const hash = murmurhash3(`${userId}:${experimentId}`) % 100;
  let cumulative = 0;
  for (const v of variants) {
    cumulative += v.weight;
    if (hash < cumulative) return v.id;
  }
  return variants[0].id;
}
// In the page component — render based on variant
export default async function HomePage() {
  const variant = headers().get("x-experiment-variant") || "control";
 
  return (
    <main>
      {variant === "control" && <HeroOriginal />}
      {variant === "variant_a" && <HeroNewDesign />}
      
      {/* Track exposure */}
      <ExperimentTracker experiment="homepage_v2" variant={variant} />
    </main>
  );
}

The Decision Framework

Use CLIENT-SIDE testing when:
  → Marketing team needs to run tests without engineering
  → Tests are simple copy/color/layout changes
  → You don't have server-side rendering capability
  → Speed of test deployment matters more than performance
  → Budget is limited (most client tools are cheaper)

Use SERVER-SIDE testing when:
  → Performance matters (e-commerce, SaaS onboarding)
  → Tests involve business logic (pricing, algorithms, features)
  → You need to test API responses or backend behavior
  → SEO is critical (no content flicker for bots)
  → You're running tests on high-traffic pages

Use EDGE-SIDE testing when (best of both):
  → You want server-side benefits without origin server changes
  → You use Vercel, Cloudflare Workers, or similar edge platforms
  → You need fast global variant assignment
  → You want to A/B test entire page versions

Edge-Side Testing (The Modern Approach)

// Vercel Edge Middleware — zero-latency variant assignment
// Runs at the edge, before the page renders
export const config = { matcher: "/" };
 
export function middleware(request: NextRequest) {
  const variant = assignVariant(request);
  
  // Rewrite to different page version at the edge
  const url = request.nextUrl.clone();
  url.pathname = variant === "control" ? "/" : "/home-v2";
  
  return NextResponse.rewrite(url, {
    headers: { "x-variant": variant },
  });
}

The result: server-side testing benefits (no flicker, no performance hit) with edge-side speed (sub-millisecond variant assignment). No origin server changes needed.

The right testing architecture depends on your team, your traffic, and your tolerance for performance trade-offs. But if you're running A/B tests on high-traffic e-commerce pages with a client-side tool, you're measuring your experiment's impact minus the performance penalty of the testing tool itself — and that's not a clean experiment.

Previous
Your CI/CD Pipeline Should Take Under 10 Minutes — Here's How
Insights
A/B Testing: Server-Side vs. Client-Side — The Technical Trade-offsThe GA4 Data Layer Implementation That E-Commerce Brands Actually NeedYour A/B Test Isn't Statistically Significant — Here's What to Do About ItServer-Side Tracking in a Cookieless World — The Implementation GuideYour Analytics Are Double-Counting Revenue — And Nobody NoticedA/B Testing Is Lying to You — Statistical Significance Isn't EnoughServer-Side Split Testing: Why Client-Side Tools Are Costing You RevenueThe Tracking Stack That Survives iOS, Ad Blockers, and Cookie DeathHow to Run Pricing Experiments Without Destroying TrustYour Conversion Rate Is a Vanity Metric — Here's What to Track InsteadBuilding a Feature Flag System That Doesn't Become Technical DebtThe Data Layer Architecture That Makes Every Test Trustworthy

Ready to Ship?

Let's talk about your engineering challenges and how we can help.

Book a Call