Back to Blog
Implementation Guide

Next.js App Router A/B Testing with Middleware

Updated December 2026
15 min read
TL;DR

Next.js App Router A/B testing: Use middleware to assign variants at the edge, set cookies for consistency, and rewrite to variant-specific pages or pass variant via headers. This gives you zero-flicker server-side testing. Works with edge runtime and Vercel Edge Functions.

The Middleware Approach

Next.js middleware runs at the edge before the page renders. Perfect for A/B testing:

  1. User visits page → middleware intercepts request
  2. Assign variant → hash user ID or set cookie
  3. Rewrite or set header → pass variant to page
  4. Render correct variant → zero flicker

Implementation Example

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  // Get or create user ID
  let userId = request.cookies.get('user_id')?.value
  if (!userId) {
    userId = crypto.randomUUID()
  }

  // Assign variant (50/50 split)
  const hash = simpleHash(userId + 'experiment-1')
  const variant = hash % 2 === 0 ? 'control' : 'variant'

  // Set cookie for consistency
  const response = NextResponse.next()
  response.cookies.set('user_id', userId, { maxAge: 31536000 })
  response.cookies.set('experiment-1', variant, { maxAge: 31536000 })

  // Pass variant to page via header
  response.headers.set('x-experiment-variant', variant)

  return response
}

// Simple hash function
function simpleHash(str: string): number {
  let hash = 0
  for (let i = 0; i < str.length; i++) {
    hash = ((hash << 5) - hash) + str.charCodeAt(i)
    hash = hash & hash
  }
  return Math.abs(hash)
}

export const config = {
  matcher: '/pricing',
}

Using Variant in Page Component

// app/pricing/page.tsx
import { cookies } from 'next/headers'

export default function PricingPage() {
  const variant = cookies().get('experiment-1')?.value || 'control'

  return (
    <div>
      {variant === 'control' ? (
        <h1>Choose Your Plan</h1>
      ) : (
        <h1>Start Your Free Trial</h1>
      )}
      {/* Rest of page */}
    </div>
  )
}

Caching Considerations

App Router caching can interfere with A/B tests:

  • Use dynamic rendering: Add export const dynamic = 'force-dynamic'
  • Or use cookies: Reading cookies makes the page dynamic automatically
  • CDN caching: Ensure Vary: Cookie header is set
  • Test in production: Dev mode behaves differently

Pros

  • • Zero flicker (server-side)
  • • Runs at the edge (fast)
  • • Full control over logic
  • • Works with RSC

Cons

  • • Requires code changes
  • • No visual editor
  • • Caching complexity
  • • Edge runtime limitations

Easier Alternative

For most Next.js sites, client-side testing with ExperimentHQ is simpler:

  • • No code changes needed
  • • Visual editor for quick tests
  • • Minimal flicker with <5KB script
  • • Works with App Router out of the box

Get Started

Share this article

Ready to start A/B testing?

Free forever plan available. No credit card required.