Skip to main content

A/B Testing with Rudderstack and Next.js

This document is a guide on how to add GrowthBook feature flags and A/B testing to your existing Next.js application using Rudderstack for event tracking.

1. Create a GrowthBook Account

You will need a GrowthBook account. You can either run GrowthBook locally or using the cloud hosted GrowthBook at https://app.growthbook.io. If you are installing it locally, you can follow the self-hosting quick start instructions here: self hosting instructions.

2. Create a JS source in Rudderstack

From within your Rudderstack account, create a new JS source.

Add Rudderstack JS source

Name the source whatever you like, in this example I'm using GrowthBook JS. When the source is created, connect it to your BigQuery data warehouse (or whatever destination you're using for GrowthBook experiment data). You can read more about how to connect to your data destination here.

Add BigQuery destination

Once you have it connected, copy the write key, as we'll need it for the next step.

Under connections, you should now see the JS source connected to the BigQuery destination. You will also need the Data plane URL which appears near the top of the page.

Add BigQuery destination

3. Integrate Rudderstack into your Next.js application

While there is plenty of documentation on how to add Rudderstack to your Next.js application out there, none of those implementations are very Next.js like, and limit the ability of Rudderstack to integrate more deeply into your code- including using GrowthBook. Below is the integration code that we came up with to address these concerns.

install the Rudderstack Analytics package

Install the javascript SDK for Rudderstack with yarn,

yarn add rudder-sdk-js

or npm:

npm install --save rudder-sdk-js

Create Rudderstack loader

Create a rudder.js file in your Next.js project. This file will load Rudderstack's SDK in a reusable and asynchronous way.

let rudder;
async function getInstance() {
if (!rudder) {
rudder = await import("rudder-sdk-js");
rudder.load(
process.env.NEXT_PUBLIC_RUDDERSTACK_KEY,
process.env.NEXT_PUBLIC_RUDDERSTACK_HOST,
{ integrations: { All: true } }
);
await new Promise((resolve) => rudder.ready(resolve));
}

return rudder;
}

const rudderObj = {
init: getInstance,
track: (...args) => getInstance().then((r) => r.track(...args)),
getAnonymousId: async () => getInstance().then((r) => r.getAnonymousId()),
};

export default rudderObj;

If you want to add other methods, like identify, you can extend the rudderObj.

You'll also have to add the Rudderstack Key and Host to your environment variables, or add to your .env.local file:

NEXT_PUBLIC_RUDDERSTACK_KEY=<your key>
NEXT_PUBLIC_RUDDERSTACK_HOST=https://<rudderstack host>

The key is the write key from the JS source we made in step 2, and the HOST is the data plane URL.

Integrate Rudderstack into your Next.js application

In your _app.js, add the Rudderstack integration we just created

import rudder from "./rudder";

This will allow you add rudder.track() in your app anywhere you import rudder.js while sharing the same Rudderstack object.

4. Integrate the GrowthBook React SDK into our Next.js app

We first need to install the GrowthBook React SDK in our Next.js app:

yarn add @growthbook/growthbook-react

Get the API key from GrowthBook (under settings -> API or from the top of the implementation instructions) and add to your environment variables (.env.local)

NEXT_PUBLIC_GROWTHBOOK_FEATURES_URL=<GrowthBook API url>

Then we can modify the code to work with GrowthBook. Modify the file pages/_app.js to add GrowthBook and Rudderstack. Import GrowthBook (and Rudderstack, if you haven't):

import {
GrowthBook,
GrowthBookProvider,
useFeature,
} from "@growthbook/growthbook-react";
import rudder from "./rudder";

then create your GrowthBook instance:

// Create a GrowthBook instance
const growthbook = new GrowthBook({
trackingCallback: (experiment, result) => {
rudder.track("Experiment Viewed", {
experimentId: experiment.key,
variationId: result.variationId,
});
},
});
note

The names experiment Viewed, experimentId and variationId will be mapped to experiment_id and variation_id columns in the experiment_viewed table within BigQuery

Then add a useEffect hook to update Rudderstack and GrowthBook when the page changes.

export default function MyApp({ Component, pageProps }) {
useEffect(() => {
// Load feature definitions from API
fetch(process.env.NEXT_PUBLIC_GROWTHBOOK_FEATURES_URL)
.then((res) => res.json())
.then((json) => {
growthbook.setFeatures(json.features);
});

// TODO: replace with real targeting attributes
growthbook.setAttributes({
company: "foo",
browser: "foo",
url: "foo",
});

// Add in Rudderstack anonId when loaded
rudder.getAnonymousId().then((id) => {
growthbook.setAttributes({ ...growthbook.getAttributes(), id });
});
}, []);

//...
}

This code adds the id with in GrowthBook to the Rudderstack anonymous_id. If you want to load user_id as well as anonymous_id you'll have to add this id to the setAttribute, and also call the rudder.identify() with the user_id info.

Finally, wrap your Next.js project in the GrowthBookProvider component, so we can use the GrowthBook methods throughout the codebase without doing addition instantiation.

return (
<GrowthBookProvider growthbook={growthbook}>
<Component {...pageProps} />
</GrowthBookProvider>
);

All together, your _app.js should look something like this:

import "../styles/globals.css";
import {
GrowthBook,
GrowthBookProvider,
useFeature,
} from "@growthbook/growthbook-react";
import { useEffect } from "react";
import rudder from "./rudder";

// Create a GrowthBook instance
const growthbook = new GrowthBook({
trackingCallback: (experiment, result) => {
rudder.track("Experiment Viewed", {
experimentId: experiment.key,
variationId: result.variationId,
});
},
});

export default function MyApp({ Component, pageProps }) {
useEffect(() => {
// Load feature definitions from API
fetch(process.env.NEXT_PUBLIC_GROWTHBOOK_FEATURES_URL)
.then((res) => res.json())
.then((json) => {
growthbook.setFeatures(json.features);
});

// TODO: replace with real targeting attributes
growthbook.setAttributes({
company: "foo",
browser: "foo",
url: "foo",
});

// Add in Rudderstack anonId when loaded
rudder.getAnonymousId().then((id) => {
growthbook.setAttributes({ ...growthbook.getAttributes(), id });
});
}, []);

return (
<GrowthBookProvider growthbook={growthbook}>
<Component {...pageProps} />
</GrowthBookProvider>
);
}

Once you have data flowing into Rudderstack, you can set it up to work with GrowthBook by following the Rudderstack guide