28
loading...
This website collects cookies to deliver better user experience
Note: We will not get into good experimentation practices in this article. This tutorial is only to show how to effectively set up an A/B test using edge functions and measure with amplitude
npx create-next-app -e with-tailwindcss feedback-widget
exp001
and our experiment cohorts exp001-control
(purple button) and exp001-variant
(blue button).Note: We choose button color to keep it simple for this test. Changing a color is generally not an acceptable A/B test to run.
experiment
folder in your project. Within the experiment folder, we'll need two files ab-testing.js
and exp001.js
.exp001.js
file, we will name the cohorts and cookie:// experiment cohort names
export const COHORTS = ['exp001-control', 'exp001-variant'];
// experiment cookie name
export const COOKIE_NAME = 'exp001-cohort';
ab-testing
file, we will set up our traffic splittitng. At the top of the file, create a function to generate a random number:function cryptoRandom() {
return (
crypto.getRandomValues(new Uint32Array(1))[0] / (0xffffffff + 1)
);
}
crypto.getRandomValues()
- you can always use Math.random()
(we won't debate the differences between the two in this tutorial - follow good practice and use what you know best!). This function will give us a random number between 0 & 1. Next, create a function that names the cohort based on the random number above:export function getCohort(cohorts) {
// Get a random number between 0 and 1
let n = cryptoRandom() * 100;
// Get the percentage of each cohort
const percentage = 100 / cohorts.length;
// Loop through the cohors and see if the random number falls
// within the range of the cohort
return (
cohorts.find(() => {
n -= percentage;
return n <= 0;
// if error fallback to control
}) ?? cohorts[0]
);
}
getCohorts()
function above breaks the cohorts into an even split depending on the number of cohorts._middleware.js
file in our pages
directory. This middleware will run before any page request is completed.import { getCohort } from '../experiment/ab-testing';
import { COHORTS, COOKIE_NAME } from '../experiment/exp001';
export function middleware(req) {
// Get the cohort cookie
const exp001 = req.cookies[COOKIE_NAME] || getCohort(COHORTS);
const res = NextResponse.rewrite(`/${exp001}`);
// For a real a/b test you'll want to set a cookie expiration
// so visitors see the same experiment treatment each time
// they visit your site
// Add the cohort name to the cookie if its not there
if (!req.cookies[COOKIE_NAME]) {
res.cookie(COOKIE_NAME, exp001);
}
return res;
}
getCohort()
function created in step 2. It then rewrites the response to show the correct page to the visitors given cohort. Last, if there isn't a cookie and we had to get it from our getCohort()
function, we send the experiment cookie with the response so subsequent requests from the browser show the same page.index.js
file in your pages directory to [exp001].js
. This takes advantage of Nextjs' dynamic routing. To render the correct page, we need to use getStaticPaths
to define the lists of paths to be rendered. First, we'll need to import the cohorts that we created in Step 2.import { COHORTS } from '../experiment/exp001';
getStaticPaths()
function to loop through each cohort to define a path for each cohort page to be rendered to HTML at build time. We pass along the exp001
object which contains the cohort as params for the path.export async function getStaticPaths() {
return {
paths: COHORTS.map((exp001) => ({ params: { exp001 } })),
fallback: false,
};
}
useRouter
to see which cohort we are randomly assigned:import { useRouter } from 'next/router';
const router = useRouter();
const cohort = router.query.exp001;
<pre>
tag...
<div className="p-4">
<pre>{cohort}</pre>
</div>
...
[exp001].js
page should now look like this:import { useRouter } from 'next/router';
import Head from 'next/head';
import { COHORTS } from '../experiment/exp001';
export default function Cohort() {
const router = useRouter();
const cohort = router.query.exp001;
return (
<div className="flex flex-col items-center justify-center min-h-screen py-2">
<Head>
<title>Simple Vercel Edge Functions A/B Test</title>
<link rel="icon" href="/favicon.ico" />
<meta
name="description"
content="An example a/b test app built with NextJs using Vercel edge functions"
/>
</Head>
<main className="flex flex-col items-center justify-center w-full flex-1 px-20 text-center">
<h1 className="text-6xl font-bold">
Vercel Edge Functions{' '}
<span className="bg-gradient-to-r from-purple-700 to-blue-600 text-transparent bg-clip-text font-bold">
A/B Test Example
</span>{' '}
With Amplitude
</h1>
<div className="p-4">
<pre>{cohort}</pre>
</div>
</main>
</div>
);
}
export async function getStaticPaths() {
return {
paths: COHORTS.map((exp001) => ({ params: { exp001 } })),
fallback: false,
};
}
npm run dev
and you should see the current cohort + experiment cookie in the dev tools.getCohort()
function on any new request when the reset cohort button is clicked:npm i js-cookie
import Cookies from 'js-cookie'
...
const removeCohort = () => {
// removes experiment cookie
Cookies.remove('exp001-cohort');
// reloads the page to run middlware
// and request a new cohort
router.reload();
};
...
<button type="button" onClick={removeCohort}>
Reset Cohort
</button>
...
getCohort()
function.[exp001].js
code:import { useRouter } from 'next/router';
import Head from 'next/head';
import Cookies from 'js-cookie';
import { COHORTS } from '../experiment/exp001';
export default function Cohort() {
const router = useRouter();
const cohort = router.query.exp001;
const removeCohort = () => {
// removes experiment cookie
Cookies.remove('exp001-cohort');
// reloads the page to run middlware
// and request a new cohort
router.reload();
};
return (
<div className="flex flex-col items-center justify-center min-h-screen py-2">
<Head>
<title>Simple Vercel Edge Functions A/B Test</title>
<link rel="icon" href="/favicon.ico" />
<meta
name="description"
content="An example a/b test app built with NextJs using Vercel edge functions"
/>
</Head>
<main className="flex flex-col items-center justify-center w-full flex-1 px-20 text-center">
<h1 className="text-6xl font-bold">
Vercel Edge Functions{' '}
<span className="bg-gradient-to-r from-purple-700 to-blue-600 text-transparent bg-clip-text font-bold">
A/B Test Example
</span>{' '}
With Amplitude
</h1>
<div className="p-4">
<pre>{cohort}</pre>
</div>
<button type="button" onClick={removeCohort}>
Reset Cohort
</button>
</main>
</div>
);
}
export async function getStaticPaths() {
return {
paths: COHORTS.map((exp001) => ({ params: { exp001 } })),
fallback: false,
};
}