content

Make A/B testing with Next.js and middleware

Here is how to realize an A/B testing system with Next.js and middlewares. The objective is to identify the user in order to display a version of a page and not to display other versions. Each user will be assigned a version of the page in a random way to test the relevance of the different versions.


In this example we will focus on the distribution of users, the analysis part will be done with analytical tools or via another middleware.


// /pages/_middleware.js
import { NextRequest, NextResponse } from 'next/server'
import { getCurrentExperiment } from '../lib/optimize'

const COOKIE_NAME = "testab";

export function middleware(req) {
  let res = NextResponse.next()
  let cookie = req.cookies[COOKIE_NAME]

  if (!cookie) {
    let n = Math.random() * 100
    const experiment = getCurrentExperiment()
    const variant = experiment.variants.find((v, i) => {
      if (v.weight >= n) return true
      n -= v.weight
    })

    cookie = `${experiment.id}.${variant.id}`
  }

  const [, variantId] = cookie.split('.')
  const url = req.nextUrl.clone()

  if (['/marketing', '/about'].includes(url.pathname)) {
    // `0` is the original version
    if (variantId !== '0') {
      url.pathname = url.pathname.replace('/', `/${cookie}/`)
    }
    res = NextResponse.rewrite(url)
  }

  // Add the cookie if it's not there
  if (!req.cookies[COOKIE_NAME]) {
    res.cookie(COOKIE_NAME, cookie)
  }

  return res
}


// /lib/optimize
import experiments from './optimize-experiments.json'

export function getCurrentExperiment() {
  return experiments.find((exp) => exp.name === 'Green button sells more 2')
}


// optimize-experiments.json contents
[
  {
    "name": "Green button sells more 2",
    "id": "MFY6FYIySra93KqA3vs9UQ",
    "variants": [
      {
        "name": "original",
        "id": 0,
        "weight": 33.3333
      },
      {
        "name": "Variant 1",
        "id": 1,
        "weight": 33.3333
      },
      {
        "name": "Variant 2",
        "id": 2,
        "weight": 33.3333
      }
    ]
  }
]