Skip to main content

Statsig in Next.js


The following is a guide that outlines how to use Statsig with Next.js. If you just want to see the finished product, it is located in statsig-io/js-client-monorepo/samples.

Note: This guide assumes you are using the Next.js 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).

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 layout.tsx

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

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

// app/statsig-demo/layout.tsx

import { getStatsigValues } from "./StatsigHelpers"; // todo: Get values from statsig-node

// todo: Use values in @statsig/js-client
import BootstrappedStatsigProvider from "./BootstrappedStatsigProvider";
import { MY_STATSIG_CLIENT_KEY } from "./constants";

export default async function StatsigDemoLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const user = { userID: "my-user" };
const values = await getStatsigValues(user);

return (
<BootstrappedStatsigProvider
clientSdkKey={MY_STATSIG_CLIENT_KEY}
values={values}
user={user}
>
{children}
</BootstrappedStatsigProvider>
);
}

This step assumes you have some file constants.ts that houses your keys, perhaps something like:

export const MY_STATSIG_SERVER_KEY = process.env["statsig_server_key"]; // secret-***
export const MY_STATSIG_CLIENT_KEY = process.env["statsig_client_key"]; // client-***

If you run this now, you will get a bunch of errors related to the imports that do not yet exists. Lets create those next.

Create getStatsigValues

getStatsigValues is the name of our helper function that generates the values required to "Bootstrap" our Statsig client.

Create this function in the following StatsigHelpers.ts file:

// app/statsig-demo/StatsigHelpers.ts

import Statsig, { StatsigUser } from "statsig-node";
import { MY_STATSIG_CLIENT_KEY, MY_STATSIG_SERVER_KEY } from "./constants";

const isStatsigReady = Statsig.initialize(MY_STATSIG_SERVER_KEY); // <- Server Key

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

const values = Statsig.getClientInitializeResponse(
user,
MY_STATSIG_CLIENT_KEY, // <- Client Key
{
// !!! IMPORTANT - the @statsig/js-client requires djb2 hashed config names, and by default this method won't generate those
hash: "djb2",
}
);

return JSON.stringify(values);
}

Create BootstrappedStatsigProvider

Another requirement of our layout.tsx file was BootstrappedStatsigProvider.

This component is the "Client" part of our SSR and requires the "use client" directive. It will be responsible for taking the values generated by getStatsigValues and using them to configure the Statsig client.

Create this component in the following BootstrappedStatsigProvider.tsx file:

// app/statsig-demo/BootstrappedStatsigProvider.tsx

"use client";

import { useMemo, type PropsWithChildren } from "react";

import { StatsigClient, StatsigUser } from "@statsig/js-client";
import { StatsigProvider } from "@statsig/react-bindings";

type Props = PropsWithChildren & {
readonly clientSdkKey: string;
readonly user: StatsigUser;
readonly values: string;
};

export default function BootstrappedStatsigProvider({
clientSdkKey,
user,
values,
children,
}: Props): JSX.Element {
const client = useMemo(() => {
const client = new StatsigClient(clientSdkKey, user);
client.dataAdapter.setData(values);
client.initializeSync();
return client;
}, [clientSdkKey, user, values]);

return (
<StatsigProvider client={client}>
{children}
</StatsigProvider>
);
}

With the BootstrappedStatsigProvider created, we have now completed our layout.tsx file and will have access to the Bootstrapped Statsig client in any child components that are rendered.

Creating page.tsx

Now we can render a page and access our Statsig configurations.

Create the following page.tsx file:

// app/statsig-demo/page.tsx

"use client";

import { useFeatureGate } from "@statsig/react-bindings";

export default function Home() {
const gate = useFeatureGate("my_gate"); // Some gate created on console.statsig.com

return (
<>
<h1>My Gate</h1>
<p>Value: {gate.value ? "Pass" : "Fail"}</p>
<p>Reason: {gate.details.reason}</p>
</>
);
}

Result

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

statsig-demo-screenshot

Our final file structure should be:

app/
└── statsig-demo/
| ├── constants.ts
│ ├── page.tsx
│ ├── layout.tsx
│ ├── BootstrappedStatsigProvider.tsx
│ └── StatsigHelpers.ts
└── ...

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 Route Handlers for these. For the sake of this demo, we will house them under statsig-demo/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

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 route.ts file:

// app/statsig-demo/proxy/initialize/route.ts

import { StatsigUser } from "statsig-node";

import { getStatsigValues } from "../../StatsigHelpers";

export async function POST(request: Request): Promise<Response> {
const body = (await request.json()) as { user: StatsigUser };
const values = await getStatsigValues(body.user);
return new Response(values);
}

This route uses the same StatsigHelpersfile 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 route.ts file:

// app/statsig-demo/proxy/rgstr/route.ts

import { LogEventObject } from "statsig-node";

import { logEvents } from "../../StatsigHelpers"; // todo: log events with statsig-node

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

export async function POST(request: Request): Promise<Response> {
const body = (await request.json()) as LogEventBody;

await logEvents(body.events);

return new Response('{"success": true}');
}

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

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

// app/statsig-demo/StatsigHelpers.ts

const isStatsigReady = ...;

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

// 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 the BootstrappedStatsigProvider.tsx file from earlier and add the following:

// app/statsig-demo/BootstrappedStatsigProvider.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 client = new StatsigClient(MY_STATSIG_CLIENT_KEY, user, options); // <- Pass options to client
// •••
return client;
}, [user, values]);

// •••
}

This adds StatsigOptions to configure our StatsigClient and point it to our new Next.js proxy 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/statsig-demo, 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.

Screenshot 2024-06-04 at 3 09 51 PM

Our final file structure should be:

app/
└── statsig-demo/
| ├── proxy/
| | ├── initialize/
| │ | └── route.ts
| | └── rgstr/
| │ └── route.ts
| ├── constants.ts
│ ├── page.tsx
│ ├── layout.tsx
│ ├── BootstrappedStatsigProvider.tsx
│ └── StatsigHelpers.ts
└── ...

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