Skip to main content

Statsig in Next.js

Installation

To use Statsig with Next.js, install the @statsig/react-bindings and @statsig/web-analytics packages:

npm install @statsig/react-bindings @statsig/web-analytics

Add your Statsig client key to your .env.local file. You can find your key at which you can find at console.statsig.com/api_keys.

Note: The NEXT_PUBLIC_ prefix is required for this to be available on the client side.

# .env.local
NEXT_PUBLIC_STATSIG_CLIENT_KEY=client-<REPLACE_WITH_YOUR_CLIENT_KEY>

Integrating with Next.js

Statsig supports both the Page Router and App Router in Next.js. There are some differences in how you integrate Statsig into each.

To integrate Statsig into your App Router app you need to use the use client directive to render the StatsigProvider on the client side.

// app/my-statsig.tsx

"use client";

import React, { useEffect } from "react";
import {
LogLevel,
StatsigProvider,
useClientAsyncInit,
} from "@statsig/react-bindings";
import { runStatsigAutoCapture } from "@statsig/web-analytics";

export default function MyStatsig({ children }: { children: React.ReactNode }) {
const { client } = useClientAsyncInit(
process.env.NEXT_PUBLIC_STATSIG_CLIENT_KEY!,
{ userID: "a-user" },
{ logLevel: LogLevel.Debug } // Optional - Prints debug logs to the console
);

useEffect(() => {
runStatsigAutoCapture(client);
}, [client]);

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

Then, use this component in your Root layout.tsx file.

// app/layout.tsx

import MyStatsig from "./my-statsig";

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<MyStatsig>
<body>
{children}
</body>
</MyStatsig>
</html>
);
}

Now if you load your app, you should see events being sent to Statsig and if you have LogLevel set to Debug, you should see debug logs in the browsers console.

Note: You should remove the logLevel option before deploying your app to production.

Checking a Gate

'use client';

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

export default function Home() {
const gate = useGateValue("my_gate");

return (
<div>
Gate Value: {gate ? 'PASSED' : 'FAILED'}
</div>
);
}

Note: In an App Router app, you need to use the use client directive to ensure your logic runs on the frontend.

Getting a Dynamic Config

'use client';

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

export default function Home() {
const config = useDynamicConfig("my_dynamic_config");

return (
<div>
Title: {config.get('title', 'Fallback Title')}
</div>
);
}

Note: In an App Router app, you need to use the use client directive to ensure your logic runs on the frontend.

Getting an Experiment

'use client';

import { useExperiment, useLayer} from "@statsig/react-bindings";

export default function Home() {
const layer = useLayer("my_experiment_layer");
// or
const experiment = useExperiment("my_experiment");

return (
<div>
Title: {layer.get('title', 'Fallback Title')}
{/* or */}
Title: {experiment.get('title', 'Fallback Title')}
</div>
);
}

Note: In an App Router app, you need to use the use client directive to ensure your logic runs on the frontend.

Getting a Parameter Store

'use client';

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

export default function Home() {
const store = useParameterStore("my_param_store");

return (
<div>
Title: {store.get('title', 'Fallback Title')}
</div>
);
}

Note: In an App Router app, you need to use the use client directive to ensure your logic runs on the frontend.

Logging an Event

'use client';

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

export default function Home() {
const { client } = useStatsigClient();

return (
<div>
<button onClick={() => client.logEvent("my_custom_event")}>
Click Me
</button>
</div>
);
}

Note: In an App Router app, you need to use the use client directive to ensure your logic runs on the frontend.

Client Bootstrapping

To fully take advantage of Next.js with Statsig, we should bootstrap the Statsig client with the values it needs. This way, the client does not need to make a separate network request to fetch these values.

Install statsig-node

To generate the required values, we can use the Statsig server SDK (statsig-node) on our backend.

npm install statsig-node

next, add a Server key to your .env file. Note that this one is private and does not start with NEXT_PUBLIC_. You can find your key at which you can find at console.statsig.com/api_keys.

# .env.local
NEXT_PUBLIC_STATSIG_CLIENT_KEY= # No Change
STATSIG_SERVER_KEY=secret-<REPLACE_WITH_YOUR_SERVER_KEY> # <- Added this line

Integrate the Backend Logic

In our App Router example, we should add a new file statsig-backend.ts to our app folder. This file will contain the logic to initialize our Statsig server SDK and generate the bootstrap values.

// app/statsig-backend.ts

import Statsig, { StatsigUser } from "statsig-node";

const isStatsigReady = Statsig.initialize(process.env.STATSIG_SERVER_KEY!, {
environment: { tier: "development" },
});

export async function generateBootstrapValues(): Promise<{
data: string;
user: StatsigUser;
key: string;
}> {
await isStatsigReady;

const user = { userID: "a-user" };
const key = process.env.NEXT_PUBLIC_STATSIG_CLIENT_KEY!;

const values = Statsig.getClientInitializeResponse(user, key, {
hash: "djb2", //🔥 'djb2' is required. By default this would be 'sha256'.
});

return {
data: JSON.stringify(values),
user,
key,
};
}

Apply the Bootstrap Values

We now need to refactor our Root layout.tsx to call the generateBootstrapValues function and pass it to the client side.

// app/layout.tsx

// ...
import { generateBootstrapValues } from "./statsig-backend";
// ...

export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const bootstrapValues = await generateBootstrapValues();

return (
<html lang="en">
<MyStatsig bootstrapValues={bootstrapValues}>
<body>
{children}
</body>
</MyStatsig>
</html>
);
}

Finally, update the MyStatsig component to accept the bootstrapValues prop, swapping out useClientAsyncInit for useClientBootstrapInit.

// app/my-statsig.tsx

"use client";

import {
LogLevel,
StatsigProvider,
StatsigUser,
// useClientAsyncInit, // <- Remove this
useClientBootstrapInit, // <- Add this
} from "@statsig/react-bindings";
import { runStatsigAutoCapture } from "@statsig/web-analytics";
import React, { useEffect } from "react";

export default function MyStatsig({
children,
bootstrapValues,
}: {
bootstrapValues: { data: string; user: StatsigUser; key: string };
children: React.ReactNode;
}) {
// Update to using useClientBootstrapInit instead of useClientAsyncInit
const client = useClientBootstrapInit(
bootstrapValues.key,
bootstrapValues.user,
bootstrapValues.data,
{ logLevel: LogLevel.Debug } // Optional - Prints debug logs to the console
);

useEffect(() => {
runStatsigAutoCapture(client);
}, [client]);

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

If you load the app now, you should see that same as before, but this time we aren't issuing any unnecessary requests.

Proxying Network Traffic

It is possible to route all Statsig network traffic through your Next.js server. There are a few reasons why you might want to set this up.

  • 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 /log_event. We will need to setup Next.js Route Handlers for these. For the sake of this demo, we will house them under app/statsig-proxy.

Add Route /initialize

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

import { StatsigUser } from "statsig-node";

import { generateBootstrapValues } from "@/app/statsig-backend";

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

if (!json || typeof json !== "object") {
return new Response(null, { status: 400 });
}

const body = json as { user: StatsigUser };

const { data } = await generateBootstrapValues(body.user);
return new Response(data);
}

Add Route /log_event

// app/statsig-proxy/log_event/route.ts

import { LogEventObject } from "statsig-node";

import { logEvents } from "@/app/statsig-backend";

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

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

if (!json || typeof json !== "object" || !Array.isArray(json.events)) {
return new Response(null, { status: 400 });
}

const body = json as LogEventBody;

await logEvents(body.events);

return new Response(JSON.stringify({ success: true }));
}

Assign Urls

Now that we have endpoints for handling /initialize and /log_event, we need to configure that Statsig client to use them.

In our app/my-statsig.tsx file, where we created our StatsigClient, we can pass in some extra StatsigOptions.

const inst = new StatsigClient(
clientSdkKey,
user,
{
networkConfig: {
logEventUrl: "/statsig-proxy/log_event",
initializeUrl: "/statsig-proxy/initialize",
},
disableCompression: true,
disableStatsigEncoding: true,

// Optional - Prints debug logs to the console
logLevel: LogLevel.Debug
}
);

Session Replay

By including the @statsig/session-replay package in your project, you can automatically capture and log user sessions as videos. This feature is useful for debugging and understanding user behavior. Read more about Session Replay.

...

Web Analytics / Auto Capture

By including the @statsig/web-analytics package in your project, you can automatically capture common web events like clicks and page views. Read more about Web Analytics.

...