Statsig in Next.js App Router
This guide assumes you are using the Next.js App Router (There is also a specific guide for Pages 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).npx create-next-app@latest
, be sure to select Yes
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
- Yarn
npm install @statsig/js-client @statsig/react-bindings statsig-node
yarn add @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";
export default async function StatsigDemoLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const user = { userID: "my-user" };
const values = await getStatsigValues(user);
return (
<BootstrappedStatsigProvider
clientSdkKey={process.env["STATSIG_CLIENT_KEY"] }
values={values}
user={user}
>
{children}
</BootstrappedStatsigProvider>
);
}
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
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";
const isStatsigReady = Statsig.initialize(process.env["STATSIG_SERVER_KEY"]); // <- Server Key
export async function getStatsigValues(user: StatsigUser): Promise<string> {
await isStatsigReady;
const values = Statsig.getClientInitializeResponse(
user,
process.env["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:
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
.
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 StatsigHelpers
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 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.
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/statsig-demo/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.
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.