Skip to main content

Set Up the SDK

1

Install the SDK

Setup Statsig in 90 seconds using one of these two options:

Wizard

Run this wizard command in your terminal
npx @statsig/wizard@latest
Copy this pre-built prompt in your IDE
# Statsig SDK Integration for Next.js

You are a frontend engineer integrating the Statsig SDK into a **Next.js application**. Follow all steps below one by one:

---

## Full Integration Instructions

1. **Detect the package manager** by checking for:
   - `package-lock.json` → use `npm`
   - `yarn.lock` → use `yarn`
   - `pnpm-lock.yaml` → use `pnpm`

2. **Detect the Next.js router type** by checking for:
   - `app/` directory → **App Router**
   - `pages/` directory → **Pages Router**

3. **Install the Statsig package** using the correct package manager:

# For npm
npm install @statsig/react-bindings @statsig/session-replay @statsig/web-analytics

# For yarn
yarn add @statsig/react-bindings @statsig/session-replay @statsig/web-analytics

# For pnpm
pnpm add @statsig/react-bindings @statsig/session-replay @statsig/web-analytics

4. Add your Statsig client key to .env.local:

NEXT_PUBLIC_STATSIG_CLIENT_KEY=ask the user for their CLIENT KEY and use that input

5. Integrate Statsig into the app (auto-detect router type):

### If the project uses the App Router (has an app/ directory):

// Create app/my-statsig.tsx
"use client";

import React from "react";
import { LogLevel, StatsigProvider } from "@statsig/react-bindings";

export default function MyStatsig({ children }: { children: React.ReactNode }) {
  const id = typeof userID !== "undefined" ? userID : "a-user";

  const user = {
    userID: id,
    // Optional additional fields:
    // email: 'user@example.com',
    // customIDs: { internalID: 'internal-123' },
    // custom: { plan: 'premium' }
  };

  return (
    <StatsigProvider
      sdkKey={process.env.NEXT_PUBLIC_STATSIG_CLIENT_KEY!}
      user={user}
      options={{ logLevel: LogLevel.Debug }}
    >
      {children}
    </StatsigProvider>
  );
}

// Update app/layout.tsx to wrap children with MyStatsig
import MyStatsig from "./my-statsig";

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

### If the project uses the Pages Router (has a pages/ directory):

// Update pages/_app.tsx
import type { AppProps } from "next/app";
import { LogLevel, StatsigProvider } from "@statsig/react-bindings";

export default function App({ Component, pageProps }: AppProps) {
  const id = typeof userID !== "undefined" ? userID : "a-user";

  const user = {
    userID: id,
    // Optional additional fields:
    // email: 'user@example.com',
    // customIDs: { internalID: 'internal-123' },
    // custom: { plan: 'premium' }
  };

  return (
    <StatsigProvider
      sdkKey={process.env.NEXT_PUBLIC_STATSIG_CLIENT_KEY!}
      user={user}
      options={{ logLevel: LogLevel.Debug }}
    >
      <Component {...pageProps} /> {/* Preserve all existing pages */}
    </StatsigProvider>
  );
}

### Final Notes

- The system must **detect the router type** and **apply the correct integration automatically**.
- **Do not remove or change any existing JSX or layout structure** — only wrap the app with `StatsigProvider`.
- **Preserve the file's language**: if it's TypeScript (`.tsx`), keep it as TypeScript; if it's JavaScript (`.jsx`), keep it as JavaScript.
- After these steps, Statsig will be integrated across the entire app, with **feature gates, configs, and experiments** available everywhere.
2

Initialize the SDK

Statsig supports both Page Router & App Router, with some differences in integration patterns.
  • App Router
  • Page Router
First, add the keys to your .env.local file:
.env.local
# the NEXT_PUBLIC_ prefix is required for this to be available on the client side
NEXT_PUBLIC_STATSIG_CLIENT_KEY=client-<REPLACE_WITH_YOUR_CLIENT_KEY>
STATSIG_SERVER_KEY=secret-<REPLACE_WITH_YOUR_SERVER_KEY>
For App Router, install the @statsig/next package:
npm i @statsig/next
The <StatsigBootstrapProvider> creates both a Statsig Client and Server instance under the hood, and “bootstraps” the client so it can render each page without a blocking network request. this will keep your app speedy and is recommended for most users. If you need more control over your setup, our Bootstrapping and React docs can provide more guidance.Add this component around the content in your root layout.tsx file:
app/layout.tsx
import { StatsigBootstrapProvider } from "@statsig/next"

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {

  const user = {
    userID: "user-123", // add additional parameters as needed
  };
  return (
    <html lang="en">
      <body>
        <StatsigBootstrapProvider
          user={user}
          clientKey={process.env.NEXT_PUBLIC_STATSIG_CLIENT_KEY}
          serverKey={process.env.STATSIG_SERVER_KEY}
        >
          {children}
        </StatsigBootstrapProvider>
      </body>
    </html>
  );
}
See the User (StatsigUser) doc for more info on the user property. From here, you’re ready to start checking gates & experiments and sending events in any sub-file of layout.tsx!

Use the SDK

Checking a Feature Flag/Gate

Now that your SDK is initialized, let’s check a Feature Gate. Feature Gates can be used to create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always CLOSED or OFF (think return false;) by default.
  • App Router
  • Page Router
'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>
  );
}
In an App Router app, you need to use the use client directive to ensure your logic runs on the frontend.

Reading a Dynamic Config

Feature Gates can be very useful for simple on/off switches, with optional but advanced user targeting. However, if you want to be able send a different set of values (strings, numbers, and etc.) to your clients based on specific user attributes, e.g. country, Dynamic Configs can help you with that. The API is very similar to Feature Gates, but you get an entire json object you can configure on the server and you can fetch typed parameters from it. For example:
  • App Router
  • Page Router
'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>
  );
}
In an App Router app, you need to use the use client directive to ensure your logic runs on the frontend.

Getting a Layer/Experiment

Then we have Layers/Experiments, which you can use to run A/B/n experiments. We offer two APIs, but we recommend the use of layers to enable quicker iterations with parameter reuse.
  • App Router
  • Page Router
'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>
  );
}
In an App Router app, you need to use the use client directive to ensure your logic runs on the frontend.

Parameter Stores

Parameter Stores hold a set of parameters for your mobile app. These parameters can be remapped on-the-fly from a static value to a Statsig entity (Feature Gates, Experiments, and Layers), so you can decouple your code from the configuration in Statsig. Read more about Param Stores here.
  • App Router
  • Page Router
'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>
  );
}
In an App Router app, you need to use the use client directive to ensure your logic runs on the frontend.

Logging an Event

Now that you have a Feature Gate or an Experiment set up, you may want to track some custom events and see how your new features or different experiment groups affect these events. This is super easy with Statsig - simply call the Log Event API for the event, and you can additionally provide some value and/or an object of metadata to be logged together with the event:
  • App Router
  • Page Router
'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>
  );
}
In an App Router app, you need to use the use client directive to ensure your logic runs on the frontend.

Session Replay

  • App Router
  • Page Router
'use client';

import { StatsigProvider } from '@statsig/react-bindings';
import { StatsigSessionReplayPlugin } from '@statsig/session-replay';

export default function App({ children }: { children: React.ReactNode }) {
  return (
    <StatsigProvider
      sdkKey={process.env.NEXT_PUBLIC_STATSIG_CLIENT_KEY!}
      user={{ userID: 'a-user' }}
      options={{ plugins: [new StatsigSessionReplayPlugin()] }}
    >
      {children}
    </StatsigProvider>
  );
}

Web Analytics / Auto Capture

  • App Router
  • Page Router
'use client';

import { StatsigProvider } from '@statsig/react-bindings';
import { StatsigAutoCapturePlugin } from '@statsig/web-analytics';

export default function App({ children }: { children: React.ReactNode }) {
  return (
    <StatsigProvider
      sdkKey={process.env.NEXT_PUBLIC_STATSIG_CLIENT_KEY!}
      user={{ userID: 'a-user' }}
      options={{ plugins: [new StatsigAutoCapturePlugin()] }}
    >
      {children}
    </StatsigProvider>
  );
}

Stable ID

Stable ID provides a consistent device identifier. It lets you run logged-out experiments and target gates at the device level.

How Stable ID Works

  • On first initialization the SDK generates a Stable ID and stores it in localStorage under statsig.stable_id.<SDK_KEY_HASH>.
  • Subsequent sessions reuse the stored value. Each client SDK key has its own Stable ID entry.
  • Local storage is scoped per domain, so cross-domain usage requires sharing the value manually (see below).

Reading the Stable ID

  • JavaScript
  • React
const context = client.getContext();
console.log('Statsig StableID:', context.stableID);

Overriding the Stable ID

Provide a custom Stable ID through StatsigUser.customIDs.stableID if you already manage a durable device identifier.
  • JavaScript
  • React
import { StatsigClient, StatsigUser } from '@statsig/js-client';

const userWithStableID: StatsigUser = {
  customIDs: {
    stableID: 'my-custom-stable-id',
  },
};

const client = new StatsigClient('client-xyz', userWithStableID);
await client.updateUserAsync(userWithStableID);
When you override the Stable ID it is persisted to local storage, so subsequent sessions reuse your custom value.

Sharing Stable ID Across Subdomains

Add this helper script before initializing the SDK and then copy the stored value onto your user object.
<!-- cross domain id script -->
<script>!function(){let t="STATSIG_LOCAL_STORAGE_STABLE_ID";function e(){if(crypto&&crypto.randomUUID)return crypto.randomUUID();let t=()=>Math.floor(65536*Math.random()).toString(16).padStart(4,"0");return`${t()}${t()}-${t()}-4${t().substring(1)}-${t()}-${t()}${t()}${t()}`}let i=null,n=localStorage.getItem(t)||null;if(document.cookie.match(/statsiguuid=([\w-]+);?/)&&([,i]=document.cookie.match(/statsiguuid=([\w-]+);?/)),i&&n&&i===n);else if(i&&n&&i!==n)localStorage.setItem(t,i);else if(i&&!n)localStorage.setItem(t,i);else{let o=e();localStorage.setItem(t,o),function t(i){let n=new Date;n.setMonth(n.getMonth()+12);let o=window.location.host.split(".");o.length>2&&o.shift();let s=`.${o.join(".")}`;document.cookie=`statsiguuid=${i||e()};Expires=${n};Domain=${s};Path=/`}(o)}}();</script>

<!-- Manually attach stableID to user object -->
<script>
const userObj = {};
if (localStorage.getItem('STATSIG_LOCAL_STORAGE_STABLE_ID')) {
  userObj.customIDs = {
    stableID: localStorage.getItem('STATSIG_LOCAL_STORAGE_STABLE_ID'),
  };
}
const client = new Statsig.StatsigClient('<client-sdk-key>', userObj);
</script>

Aligning Stable ID Between Client and Server

To share Stable ID with a backend Statsig SDK, send the value with requests and persist it server-side when missing. The server can bootstrap the client with the same Stable ID.
// Server: ensure Stable ID exists, then return initialize response for the client
const values = Statsig.getClientInitializeResponse(user, YOUR_CLIENT_KEY, {
  hash: 'djb2',
});

// Client: apply the server-provided values and initialize synchronously
const { values, user: verifiedUser } = await fetch('/init-statsig-client', {
  method: 'POST',
  body: loadUserData(),
}).then((res) => res.json());

const myClient = new StatsigClient(YOUR_CLIENT_KEY, verifiedUser);
myClient.dataAdapter.setData(values);
myClient.initializeSync();

Advanced Setup

  • App Router
  • Page Router
app/api/statsig-bootstrap/route.ts
import { Statsig, StatsigUser } from '@statsig/statsig-node-core';

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

  // Ensure server SDK is initialized at startup
  // await Statsig.initialize(process.env.STATSIG_SERVER_KEY!);

  const values = Statsig.getClientInitializeResponse(user, {
    hashAlgorithm: 'djb2',
  });
  return new Response(JSON.stringify(values), { status: 200 });
}
app/layout.tsx
import { StatsigBootstrapProvider } from '@statsig/next';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  const user = { userID: 'user-123' };
  return (
    <html lang="en">
      <body>
        <StatsigBootstrapProvider
          user={user}
          clientKey={process.env.NEXT_PUBLIC_STATSIG_CLIENT_KEY}
          serverKey={process.env.STATSIG_SERVER_KEY}
        >
          {children}
        </StatsigBootstrapProvider>
      </body>
    </html>
  );
}

Proxying Network Traffic (Optional)

  • App Router
  • Page Router
app/statsig-proxy/initialize/route.ts
import { generateBootstrapValues } from './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 data = await generateBootstrapValues();
  return new Response(data);
}
app/statsig-proxy/log_event/route.ts
type ExtendedRequestInit = RequestInit & { duplex?: 'half' | 'full' };

export async function POST(request: Request): Promise<Response> {
  const tail = request.url.split('?').pop();
  const logEventUrl = `https://events.statsigapi.net/v1/log_event?${tail}`;
  const fetchOptions: ExtendedRequestInit = {
    method: 'POST',
    body: request.body,
    headers: request.headers,
    duplex: 'half',
  };
  return fetch(logEventUrl, fetchOptions);
}
// Assign URLs when creating the client
const inst = new StatsigClient(clientSdkKey, user, {
  networkConfig: {
    logEventUrl: '/statsig-proxy/log_event',
    initializeUrl: '/statsig-proxy/initialize',
  },
  disableCompression: true,
  disableStatsigEncoding: true,
});

Statsig Site Generation (SSG)

Vercel’s Static Site Generation renders HTML at build time. Because static HTML can’t be responsive to per-user values, experimenting on SSG content requires one of these patterns:
  • Use Vercel Edge Middleware with Statsig’s Edge Config Adapter for zero-latency redirects.
  • Isolate Statsig usage to hydrated client components only.
// Create a single client and share it across multiple StatsigProviders
const myStatsigClient = new StatsigClient(YOUR_SDK_KEY, user, options);
await myStatsigClient.initializeAsync();

<StatsigProvider client={myStatsigClient}>
  <YourComponent />
</StatsigProvider>

<StatsigProvider client={myStatsigClient}>
  <AnotherComponent />
</StatsigProvider>

Statsig Options

loggingEnabled
LoggingEnabledOption
default:"browser-only"
Controls logging behavior.
  • browser-only (default): log events from browser environments.
  • disabled: never send events.
  • always: log in every environment, including non-browser contexts.
disableLogging
boolean
deprecated
deprecated
Use loggingEnabled: 'disabled' instead.
disableStableID
boolean
default:"false"
Skip generating a device-level Stable ID.
disableEvaluationMemoization
boolean
default:"false"
Recompute every evaluation instead of using the memoized result.
initialSessionID
string
Override the generated session ID.
enableCookies
boolean
default:"false"
Persist Stable ID in cookies for cross-domain tracking.
disableStorage
boolean
Prevent any local storage writes (disables caching).
networkConfig
NetworkConfig
Override network endpoints per request type.
environment
StatsigEnvironment
Set environment-wide defaults (for example { tier: 'staging' }).
logLevel
LogLevel
default:"Warn"
Console verbosity.
loggingBufferMaxSize
number
default:"50"
Max events per log batch.
loggingIntervalMs
number
default:"10_000"
Interval between automatic flushes.
overrideAdapter
OverrideAdapter
Modify evaluations before returning them.
includeCurrentPageUrlWithEvents
boolean
default:"true"
Attach the current page URL to logged events.
disableStatsigEncoding
boolean
default:"false"
Send requests without Statsig-specific encoding.
logEventCompressionMode
LogEventCompressionMode
default:"Enabled"
Control compression for batched events.
disableCompression
boolean
deprecated
deprecated
Use logEventCompressionMode instead.
dataAdapter
EvaluationsDataAdapter
Provide a custom data adapter to control caching/fetching.
customUserCacheKeyFunc
CustomCacheKeyGenerator
Override cache key generation for stored evaluations.
api
string
default:"https://api.statsig.com"
Base URL for all requests (append /v1).
logEventUrl
string
default:"https://prodregistryv2.org/v1/rgstr"
Endpoint for event uploads.
logEventFallbackUrls
string[]
Fallback endpoints for event uploads.
networkTimeoutMs
number
default:"10000"
Request timeout in milliseconds.
preventAllNetworkTraffic
boolean
Disable all outbound requests; combine with loggingEnabled: 'disabled' to silence log warnings.
networkOverrideFunc
function
Provide custom transport (e.g., Axios).
initializeUrl
string
default:"https://featureassets.org/v1/initialize"
Endpoint for initialization requests.

Additional Resources

I