← Back to all how-tos

CMS Features

Implement A/B Testing

Configure A/B Testing variants in React Bricks, resolve the active variant in middleware, and fetch the right page variant in Next.js.

Estimated time: 12 minRaw Markdown

React Bricks A/B Testing uses page variants.

Editors create multiple variants of the same page, assign weights to active variants, and React Bricks serves them according to those weights.

In code, your app needs to:

  • resolve the active variant for each request
  • keep the same visitor on the same variant
  • pass the selected variant to fetchPage
  • track variant impressions in your analytics provider

Docs reference: A/B Testing and Multischeduling

Configure variants in React Bricks

Open the page you want to test in React Bricks and create the variants you need.

Variants can be used for two related workflows:

  • multischeduling, where each variant has its own publish and unpublish window
  • A/B Testing, where more than one variant is active at the same time and each active variant has a weight

For example:

VariantWeight
A50
B50

This creates an even split. To run a 70 / 30 test, set the variant weights accordingly.

Add the A/B Testing middleware

The middleware resolves the active variant for the incoming request and stores it in a cookie, so a visitor keeps seeing the same variant across requests.

In current Next.js App starters, middleware.ts can chain A/B Testing with i18n:

import { NextResponse } from 'next/server'
import {
  chain,
  createI18nMiddleware,
  createWithAbTestingMiddleware,
} from 'react-bricks/rsc'
 
import { i18n } from '@/i18n-config'
import { abTestingEnabled, getConfig } from './react-bricks/getConfig'
 
const rbConfig = getConfig()
 
const withAbTestingMiddleware = createWithAbTestingMiddleware({
  i18n,
  config: rbConfig,
})
const withI18nMiddleware = createI18nMiddleware({ i18n, NextResponse })
 
const middleware = abTestingEnabled
  ? chain([withAbTestingMiddleware, withI18nMiddleware])
  : withI18nMiddleware
 
export default middleware
 
export const config = {
  // Matcher ignoring `/_next/` and `/api/`
  matcher: [
    '/((?!api|_next/static|_next/image|favicon.ico|admin|logo.svg|bricks-preview-images|masks|preview).*)',
  ],
}

Use createWithAbTestingMiddleware in Next.js App Router projects where A/B Testing must work together with i18n.

See the middleware reference docs for the details:

Fetch the selected variant

In your App Router page, read the selected variant from the cookie and pass it to fetchPage as variantName.

app/[lang]/[[...slug]]/page.tsx
import { cookies } from 'next/headers'
import { fetchPage, getAbTestingCookie, types } from 'react-bricks/rsc'
 
import config from '@/react-bricks/config'
 
const getData = async (
  slug: string | string[] | undefined,
  locale: string
): Promise<{
  page: types.Page | null
  variantName?: string
  testName?: string
}> => {
  const cleanSlug = !slug
    ? '/'
    : typeof slug === 'string'
      ? slug
      : slug.join('/')
 
  const cookieStore = await cookies()
  const variantName = getAbTestingCookie({
    slug: cleanSlug,
    locale,
    cookieStore,
  })
 
  const page = await fetchPage({
    slug: cleanSlug,
    language: locale,
    variantName,
    config,
    fetchOptions: { next: { revalidate: 3 } },
  }).catch(() => null)
 
  return {
    page,
    variantName,
    testName: `${cleanSlug}_${locale}`,
  }
}

getAbTestingCookie reads the variant selected by the middleware. Passing that value to fetchPage keeps the rendered content aligned with the visitor's assigned variant.

Docs reference: getAbTestingCookie

Track the experiment

React Bricks decides which variant should be rendered, but your analytics provider measures impressions.

The Next.js App Router starter defines a small GAExperimentTracker component for this.

This is not a React Bricks exported component. It is a project component that sends an experiment_impression event to Analytics with the exp_variant_string parameter, composed from the experiment name and variant name.

components/GAExperimentTracker.tsx - Next.js App Router example
'use client'
 
import { sendGTMEvent } from '@next/third-parties/google'
import { useEffect } from 'react'
 
interface GAExperimentTrackerProps {
  testName: string
  variantName: string
}
 
export default function GAExperimentTracker({
  testName,
  variantName,
}: GAExperimentTrackerProps) {
  useEffect(() => {
    sendGTMEvent({
      event: 'experiment_impression',
      exp_variant_string: `${testName}.${variantName}`,
    })
  }, [testName, variantName])
 
  return null
}

Then render the tracker when both identifiers are available:

{
  testName && variantName && (
    <GAExperimentTracker testName={testName} variantName={variantName} />
  )
}

With this setup, Analytics receives an experiment_impression event for the rendered variant.

The feature/ab-testing branch of the React Bricks starters repository includes a complete implementation example, including middleware setup and analytics integration: reactbricks-starters feature/ab-testing

There is also a concrete Next.js App example that sends the test name and variant name to Google Analytics: Next.js App analytics example

Check the result

After wiring A/B Testing:

  • create two active variants for a page
  • set variant weights in React Bricks
  • visit the page in a private browser session
  • confirm the middleware stores a stable variant cookie
  • refresh and confirm the same variant is served
  • verify the selected variantName is passed to fetchPage
  • check that Analytics receives an experiment_impression event with the correct exp_variant_string

Related docs