Skip to main content

Statsig in Next.js Pages Router

The following is a guide that outlines how to use Statsig with Next.js Pages Router. (There is also a specific guide for App Router). If you just want to create a new project to follow the guide, you can run npx create-next-app@latest (See create-next-app).

When using npx create-next-app@latest, be sure to select No when asked "Would you like to use App Router?"

Installation

To fully utilize Statsig across Next.js, you will need both Server and Client SDKs installed. @statsig/js-client and @statsig/react-bindings for the client side and statsig-node for the server side.

npm install @statsig/js-client @statsig/react-bindings statsig-node

Usage

Since Next.js supports Server Side Rendering (SSR), we should setup Statsig to take advantage of this.

Create StatsigServerUtil.ts

Let's start by create a util function to get values from the Statsig Node SDK.

Let's start by creating a new route that our Statsig integration will live on. For the sake of this guide, we will put it under a statsig directory.

Create the following StatsigServerUtil.ts file. This file will be responsible for the "Server" part of our SSR:

// pages/statsig/StatsigServerUtil.tsx

import Statsig, { StatsigUser } from "statsig-node";
import { StatsigProps } from "./StatsigProps"; // todo: add StatsigProps.ts

const STATSIG_SERVER_KEY = process.env['STATSIG_SERVER_KEY'];
const STATSIG_CLIENT_KEY = process.env['STATSIG_CLIENT_KEY'];

const isStatsigReady =
typeof window !== "undefined"
? Promise.reject("DO NOT RUN SERVER CODE ON CLIENT")
: Statsig.initialize(STATSIG_SERVER_KEY);

export async function getStatsigValues(
user: StatsigUser
): Promise<StatsigProps> {
await isStatsigReady;

const values = Statsig.getClientInitializeResponse(user, undefined, {
hash: "djb2",
});

return {
clientKey: STATSIG_CLIENT_KEY,
user,
values: JSON.stringify(values),
};
}

This step assumes you have your Statsig SDK keys stored as environment variables.

Something like this:

STATSIG_SERVER_KEY="secret-***" STATSIG_CLIENT_KEY="client-***" npm start

The first step referenced another file StatsigProps.ts. This file contains the type for our Page props.

Let's create it next to our StatsigServerUtil file:

// pages/statsig/StatsigProps.ts

import { StatsigUser } from "statsig-node";

export type StatsigProps = {
clientKey: string;
user: StatsigUser;
values: string;
};

Great, we now have our server side setup to generate values for a given StatsigUser, but it's not being used any where, let's change that.

Calling Statsig From getServerSideProps

Since Statsig operates on user information (StatsigUser), we cannot really fetch any information before we know anything about our user. This requirement rules out usage of getStaticProps, but luckily, Next.js provides a function specifically related to fetching based on user information, getServerSideProps.

To add Statsig to a page, we can add a call to StatsigServerUtil's getStatsigValues function in getServerSideProps.

Add the following to your root page index.tsx (Or any page really):

// pages/index.tsx

import { getStatsigValues } from "./statsig/StatsigServerUtil"; // <-- The file we created earlier
import { useFeatureGate } from "@statsig/react-bindings";

export const getServerSideProps = async () => {
const statsigProps = await getStatsigValues({ userID: "a-user" });

return { props: { statsigProps } };
};

export default function Page() {
const { value, details } = useFeatureGate("my_gate"); // <-- a FeatureGate you created on console.statsig.com

return (
<div style={{ padding: 16 }}>
my_gate: {value ? "Passing" : "Failing"} ({details.reason})
</div>
);
}

We are now making a call to get the values for a specific user, but we aren't actually using it yet. If you were to visit this page now, you would see my_gate: Failing (Error:NoClient), this is because we are calling a Statsig hook (useFeatureGate) without a StatsigProvider. To fix this, we will setup a StatsigProvider in an App Layout.

Add StatsigProvider to _app.tsx

In our main _app.tsx file, we will attempt to provide Statsig to all child components. We will do this conditionally so that not all our pages need to call getStatsigValues in getServerSideProps.

In your _app.tsx add the following:

// pages/_app.tsx

import { StatsigProvider } from "@statsig/react-bindings";
import type { AppProps } from "next/app";

export default function App({ Component, pageProps }: AppProps) {
const [client] = useState(() => {
if (!pageProps.statsigProps) {
return null; // the page did not call getStatsigValues values in getServerSideProps
}

const { user, values, clientKey } = pageProps.statsigProps as StatsigProps;

const inst = new StatsigClient(clientKey, user);

inst.dataAdapter.setData(values); // requires statsig-node 5.20.0 or above. Older version should call setDataLegacy.
inst.initializeSync();

return inst;
});

if (client) {
return (
<StatsigProvider client={client}>
<Component {...pageProps} />
</StatsigProvider>
);
}

return <Component {...pageProps} />;
}

Result

Having followed all the above steps and creating all the files, we should now be able to load the demo page locally http://localhost:3000 and see something like:

statsig-demo-screenshot

Our final file structure should be:

pages/
├── api/
│ └── ...
└── statsig/
│ ├── StatsigProps.ts
│ └── StatsigServerUtil.ts
├── _app.tsx
├── index.tsx
└── ...

This completes the basic Statsig integration into Next.js. You can stop here if all you needed was SSR, but we will now move onto more advanced topics around Proxying network request through your Next.js server.

Advanced - Network Proxy

There are a few reasons why you might want to setup a proxy for your Statsig client.

  • Avoid ad blockers
  • Keep network traffic within your own cluster
  • Maintain your own event filtering/de-duplication logic

The Statsig client uses two main endpoints. /initialize and /rgstr. We will need to setup Next.js API Routes for these. For the sake of this demo, we will house them under api/statsig-proxy.

note

It is possible to use custom names for your routes, but you should avoid using words like 'event' or 'analytics' as these might trigger some ad blockers.

See StatsigOptions.networkConfig

Create /initialize

The /initialize endpoint supports POST requests and is used for fetching evaluation data for a given StatsigUser.

Let's support this endpoint by creating the following initialize.ts api route file:

// pages/api/statsig-proxy/initialize.ts

import { getStatsigValues } from "../../statsig/StatsigServerUtil";
import type { NextApiRequest, NextApiResponse } from "next";
import { StatsigUser } from "statsig-node";

export default async function handler(
req: NextApiRequest,
res: NextApiResponse<string>
) {
if (req.method !== "POST") {
res.status(400).send("/initialize only supports POST");
return;
}
console.log(typeof req.body);

const { user } = JSON.parse(req.body) as { user: StatsigUser };
const { values } = await getStatsigValues(user);
res.status(200).send(values);
}

This route uses the same StatsigServerUtil file we created early to generate values for the given StatsigUser.

Create /rgstr

The /rgstr endpoint supports POST requests and is used to logging events from the StatsigClient.

Let's support this endpoint by creating the following rgstr.ts api route file:

// pages/api/statsig-proxy/rgstr.ts

import { LogEventObject } from "statsig-node";
import { logEvents } from "../../statsig/StatsigServerUtil";
import type { NextApiRequest, NextApiResponse } from "next";

type LogEventBody = {
events: LogEventObject[];
};

export default async function handler(
req: NextApiRequest,
res: NextApiResponse<string>
) {
if (req.method !== "POST") {
res.status(400).send("/rgstr only supports POST");
return;
}
const { events } = JSON.parse(req.body) as LogEventBody;

await logEvents(events);
res.status(202).send('{"success": true}');
}

This endpoint requires a new helper function called logEvent to be added to StatsigServerUtil. This function will use the existing statsig-node server instance to log events to Statsig.

Open up the StatsigServerUtil.ts file from earlier and add the following:

// pages/statsig/StatsigServerUtil.tsx

const isStatsigReady = ...;

export async function getStatsigValues(user: StatsigUser): Promise<StatsigProps> {
// •••
}

// Add new function:
export async function logEvents(events: LogEventObject[]): Promise<void> {
await isStatsigReady;

events.forEach((event) => Statsig.logEventObject(event));
}

Configure StatsigClient

With our two routes added, we now need to tell our StatsigClient instance about them.

Open up our _app.tsx file and add the following:

// pages/_app.tsx

import { ..., type StatsigOptions /* Add new import */ } from "@statsig/js-client";

// •••

export default function BootstrappedStatsigProvider(...): JSX.Element {
const client = useMemo(() => {
// Add new StatsigOptions:
const options: StatsigOptions = {
networkConfig: {
api: "http://localhost:3000/statsig-demo/proxy", // Your Next.js server
},
disableStatsigEncoding: true,
disableCompression: true,
};

const inst = new StatsigClient(clientKey, user, options); // <- Pass options to client
// •••
return inst;
}, [user, values]);

// •••
}

This adds StatsigOptions to configure our StatsigClient and point it to our new Next.js api routes.

It also disables any encoding and compression, so the requests we receive use plain Json objects and can be used by our server. Without this, your server will not be able to understand the request bodies.

note

It is also possible to only override one endpoint and leave the others to go directly to Statsig. To do this, instead of override api, we would override the specific url like:

const options: StatsigOptions = {
networkConfig: {
initializeUrl: 'http://localhost:3000/api/statsig-proxy/initialize', // Full /initialize Url
},
disableStatsigEncoding: true,
disableCompression: true,
};

Result

Now, when we visit our demo page http://localhost:3000, we should see traffic flowing to our Next.js server, rather than going to Statsig directly.

This can be confirmed by viewing the network tab of your browser.

network-tab-demo

Our final file structure should be:

pages/
├── api/
│ ├── statsig-proxy/
│ │ ├── initialize.ts
│ │ └── rgstr.ts
│ └── ...
└── statsig/
│ ├── StatsigProps.ts
│ └── StatsigServerUtil.ts
├── _app.tsx
├── index.tsx
└── ...

The complete source code for this guide can be found here.