This content originally appeared on DEV Community and was authored by Jeremy Dorn
Today, Vercel released Next.js 12 which adds a number of exciting performance improvements as well as a new beta feature - Middleware. Middleware has many uses, but I'm going to focus in this post on A/B Testing.
You've always been able to run A/B tests on Next.js applications, but until this latest release there have been some major strings attached. For example, on static pages, there would often be a "flash" where users would see the original page for a split second before your variation popped in and replaced it. And on server-rendered pages, you would need to completely disable caching since two users on the same URL could get two different HTML responses.
Next.js middleware fixes these issues in an elegant way. You can create two different pages for a single URL and route traffic between them with a middleware function. The middleware is run at the edge, so it's globally distributed and super fast for your users.
Let's start with a simple Next.js app structure:
pages/
| index.tsx
| index_new.tsx
| _middleware.ts
lib/
| abtesting.ts
Our existing homepage (pages/index.tsx
) is kind of boring:
/* pages/index.tsx */
import Link from "next/link"
export default function HomePage() {
return <>
<h1>My Site</h1>
<Link href="/signup"><a>Sign Up</a></Link>
</>
}
We want to see if we can increase signups, so we clone the existing homepage into a new file (pages/index_new.tsx
) and change the text to be more exiciting:
/* pages/index_new.tsx */
import Link from "next/link"
export default function HomePageVariant() {
return <>
<h1>Welcome to My Site!</h1>
<Link href="/signup"><a>Get Started Today!</a></Link>
</>
}
Now the real magic happens in the pages/_middleware.ts
file:
/* pages/_middleware.ts */
import { NextRequest, NextResponse } from 'next/server'
import {
initRequest,
getExperimentContext,
trackRequest
} from "../lib/abtesting";
export function middleware(req: NextRequest) {
initRequest(req)
// Default to normal Next.js routing
let res = NextResponse.next()
// If the user is on the homepage, run the experiment
const { pathname } = req.nextUrl;
if (pathname === "/") {
const ctx = getExperimentContext(req)
const { value: newPath } = ctx.run({
key: "homepage-copy-experiment",
variations: ["/", "/index_new"]
})
// Route to the assigned page path
res = NextResponse.rewrite(newPath)
}
// Server-side analytics tracking
trackRequest(req, res)
return res
}
The middleware file references a few helper functions in lib/abtesting.ts
. For dependencies, I'm using GrowthBook to run the A/B tests and Mixpanel for analytics tracking. Feel free to swap these out with other libraries if you want.
/* lib/abtesting.ts */
import { GrowthBook } from "@growthbook/growthbook"
import { NextRequest, NextResponse } from "next/server"
import Mixpanel from "mixpanel"
const COOKIE_NAME = 'distinct_id'
const mixpanel = Mixpanel.init(process.env.MIXPANEL_TOKEN)
// Get the user's ip and distinct_id from the request
// Generate a new distinct_id if it doesn't exist yet
let distinct_id: string, ip: string
export function initRequest(req: NextRequest) {
distinct_id = req.cookies[COOKIE_NAME] || crypto.randomUUID()
ip = req.ip
}
// Function to get the GrowthBook A/B testing client
export function getExperimentContext(req: NextRequest) {
return new GrowthBook({
user: {id: distinct_id},
trackingCallback: (experiment, result) => {
mixpanel.track("Experiment Viewed", {
distinct_id,
ip,
experimentId: experiment.key,
variationId: result.variationId,
})
}
})
}
export function trackRequest(req: NextRequest) {
// Track the page view in Mixpanel
mixpanel.track("Page View", {
distinct_id,
ip,
path: req.nextUrl.pathname
})
// Persist the distinct_id in a cookie for future requests
if (!req.cookies[COOKIE_NAME]) {
res.cookie(COOKIE_NAME, distinct_id)
}
}
That's all we need! Now, when a user visits your site, the following will happen:
- If there is no distinct_id cookie yet, we generate a new UUID.
- If the user is requesting the homepage, we randomly assign them a variation - either
/
or/index_new
- and overwrite the default Next.js routing to use the assigned path. - We track the page view (and which variation the user was assigned) in Mixpanel.
- If we needed to generate a new UUID in step 1, we store it in a cookie in the response.
Hopefully in future releases, Next.js expands on this feature to make A/B testing even more powerful. Imagine, for example, that middleware could inject props into your pages, similar to getServerSideProps
. Then you wouldn't need to create new temporary pages every time you wanted to run an A/B test!
This content originally appeared on DEV Community and was authored by Jeremy Dorn
Jeremy Dorn | Sciencx (2021-10-26T20:34:54+00:00) A/B Testing with the new Next.js 12 Middleware. Retrieved from https://www.scien.cx/2021/10/26/a-b-testing-with-the-new-next-js-12-middleware/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.