Statsig in Next.js Pages Router
npx create-next-app@latest
(See create-next-app).npx create-next-app@latest
, be sure to select No
when asked "Would you like to use App Router?"Installation
You can install the Statsig SDK via npm or yarn:
- NPM
- Yarn
npm install @statsig/js-client @statsig/react-bindings
yarn add @statsig/js-client @statsig/react-bindings
Usage
In the Page Router model, you will need to wrap your app in StatsigProvider
which will provide Statsig context throughout your NextJS app.
Modify your _app.tsx
to match the following
// _app.tsx
import type { AppProps } from "next/app";
import { StatsigProvider } from "@statsig/react-bindings";
export default function App({ Component, pageProps }: AppProps) {
return (
<StatsigProvider sdkKey="client-KEY" user={{ userID: "20" }}>
<Component {...pageProps} />
</StatsigProvider>
);
}
With that wrapper, you're all set to use Statsig gates, experiments and configs.
Feature Flags/Gates
You can check gates by using useGateValue
hook in any client side rendered page. In this case, let's modify page.tsx
to check for a gate value:
// index.tsx
import { useGateValue } from "@statsig/react-bindings";
export default function Home() {
const gate = useGateValue("check_user")
return (
<div>
Gate Value: {gate ? 'PASSED' : 'FAILED'}
</div>
);
}
Experiments
You can reach into the experiment variant and access parameters that are assigned to the specific user by using the useExperiment hook like this:
// index.tsx
import { useExperiment } from "@statsig/react-bindings";
export default function Home() {
const experiment = useExperiment("headline_test")
return (
<div>
Headline Parameter: {experiment.get('headline', 'Default')}
</div>
)
}
Updating user properties (e.g., Login)
Sometimes you'll need to update user properties, say when the user logs in and a userID
is assigned, or a set of new properties have been identified. This would require Statsig to go fetch new values for all the gates, experiments and config evaluations. This is achieved by the useStatsigUser
hook.
Create the file pages/login.tsx
and put this code in. Click on the Login button to update the user.
// pages/login.tsx
import { useGateValue, useStatsigUser } from "@statsig/react-bindings";
export default function LoginPage() {
const gateValue = useGateValue("check_user");
const { updateUserAsync } = useStatsigUser();
return (
<div>
<div>Gate is {gateValue ? 'passing' : 'failing'}.</div>
<button onClick={() => updateUserAsync({ userID: "2" })}>
Login
</button>
</div>
);
}
Server Side Rendering (SSR)
Since Next.js supports Server Side Rendering (SSR), we could setup Statsig to take advantage of this.
Install
If you intend to use 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
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:
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
.
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.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.
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.
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.