Skip to main content

TL;DR

This guide adds one production feature flag to a Next.js App Router application without creating server/client disagreement or an unsafe dependency on remote configuration. It gives requests a stable identity, resolves the same decision across Server Components, Client Components, and Route Handlers, keeps the legacy checkout as the code fallback, and separates evaluation logs from experiment exposure events. The rollout follows a 0 → 1 → 100 sequence: deploy a disabled foundation, prove one internal path with parity and failure tests, then expand deliberately and remove the flag only after the new checkout becomes the code default. This guide is optimized for AI coding agents, and it is recommended that you hand it off to your agent of choice for implementation.

Guide map

Give this guide to your coding agent

Copy this page’s URL and the prompt below into a coding agent that can inspect your repository. Replace REPLACE_WITH_REPOSITORY with the repository path or name and REPLACE_WITH_GUIDE_URL with this page’s public URL. Start in a branch, worktree, or disposable clone, and use a non-production GrowthBook environment for the first pass.
Use this guide when the request sounds simple:
Add feature flags to this Next.js app.
The first Boolean is easy. The production task is larger. The same decision must resolve consistently in a Server Component, a Client Component, and a Route Handler. Anonymous users need durable identities. Logged-in users and organizations need an explicit randomization unit. A missing payload must select known-safe behavior. A flag change must propagate on a bounded schedule. Experiment exposure events must mean what their name says. The code also needs a final state in which the temporary branch and flag no longer exist. This guide implements that complete path for a Next.js App Router application. It starts with a disabled new-checkout-flow flag, enables it for internal users, expands it through a deterministic percentage rollout, and ends by deleting the flag after the new path becomes the code default.

Task contract

Task: Put a new checkout flow behind a GrowthBook feature flag and operate it from 0% to 100% exposure without requiring a deployment for each rollout step. Use this guide when:
  • The repository uses Next.js App Router.
  • At least 1 decision happens in a Server Component, Server Function, or Route Handler.
  • The team needs runtime targeting, gradual rollout, a kill switch, or an experiment path.
  • The old implementation can remain available during rollout.
  • The application runs with the Node.js runtime for the server-side integration shown here.
Do not use this guide when:
  • The value is build-time configuration such as a public API base URL.
  • The condition is authorization, an entitlement, a billing limit, or a security boundary. Keep those checks in the authoritative server-side policy layer.
  • The change cannot safely support 2 implementations at once, such as an incompatible destructive database migration without a compatibility phase.
  • The application is a static export with no request-time server. Use a browser SDK integration instead and accept that the SDK payload and client-visible rules reach the browser.
  • A single environment variable, changed only through a reviewed deployment, satisfies the full lifecycle.
Tested stack: Next.js 16.3.0, React 19.2.8, TypeScript 5, flags 4.3.0, @flags-sdk/growthbook 0.3.1, and GrowthBook JavaScript/React SDK 1.7.0. Next.js 16 renamed middleware.ts to proxy.ts and made cookies() and headers() asynchronous. For Next.js 15, keep the same identity design but use the file convention and APIs supported by that version. Required access:
  • Read and edit access to the Next.js repository.
  • A GrowthBook organization with permission to create an SDK Connection and a feature flag.
  • Environment-variable access for local, preview, staging, and production deployments.
  • Access to server logs or the application’s analytics pipeline.
  • A non-production environment in which the new and old paths can both be exercised.
Files created or changed in the primary implementation:
End state:
  • The old checkout remains the code fallback until the rollout is complete.
  • The same request identity receives the same decision in the page and API.
  • A missing, disabled, malformed, timed-out, or unreachable GrowthBook payload selects the old checkout.
  • Server logs distinguish feature evaluation from experiment exposure.
  • Development, staging, and production use SDK keys tied to their own environments.
  • CI rejects unknown feature keys and exercises both code paths.
  • The team can turn the new path off, verify the rollback, and later remove both the old path and the flag.
Rollback: Set the production feature environment to disabled or publish a top-priority rule that forces false. Verify the page and the API return the legacy path for a known identity. Do not delete the flag during an incident. Deletion removes it from the SDK payload, which invokes the code fallback, but it also discards the clearest control-plane receipt while responders are diagnosing the problem.

First inspect the repository

Do not install an SDK until you know where the decision must run. Run these commands from the application root:
Use PowerShell equivalents on Windows:
Record these answers before changing code:
  1. Is the repository App Router, Pages Router, or mixed?
  2. Is the checkout route rendered dynamically or prerendered?
  3. Which part of the decision belongs on the server?
  4. Does a Client Component only need the resolved value, or must it evaluate flags independently after hydration?
  5. What authenticated identity already exists: user, account, workspace, organization, or none?
  6. What anonymous identifier already exists, and who owns its consent and retention policy?
  7. Does an analytics event pipeline already reach the data source used for experimentation?
  8. Does the deployment run Node.js, Edge, static export, or a mixture?
  9. Is the application deployed on Vercel, self-hosted as multiple Next.js instances, or hosted on another platform?
  10. What is the safe behavior when configuration cannot load?
If any answer is unknown, preserve the old path and make false the fallback. A coding agent should not infer an identity strategy, analytics contract, or safe failure state from a component name.

Repository decision tree

Follow this decision tree:
The default in this guide is the first server-side branch: @flags-sdk/growthbook. It fits App Router’s request model, provides a request-deduplicated identify function, supports Vercel Flags tooling, and keeps flag definitions in one typed module. The direct JavaScript and React SDK path appears later as a coherent alternative, not as code to mix into the same decision.

Choose the integration boundary

The packages overlap, but they solve different integration problems. Use only 1 evaluation owner for a particular decision. If a Server Component evaluates new-checkout-flow, pass that Boolean into the Client Component. Do not evaluate it again in the browser just because the component contains "use client". Re-evaluating can create a hydration mismatch, a different assignment, and a duplicate experiment exposure. The GrowthBook Next.js adapter reference, JavaScript SDK reference, and React SDK reference cover each package separately. This guide covers the application architecture between them.

Create the GrowthBook environment boundary

Create separate SDK Connections for development, staging, and production. Each connection maps to 1 GrowthBook environment and has its own client key. The production process must receive only the production key. See GrowthBook environments and SDK connections. In GrowthBook:
  1. Open SDK Configuration.
  2. Confirm the built-in dev, staging, and production environments exist.
  3. Create a Next.js/Flags SDK Connection for each environment used by the application.
  4. Copy each connection’s client key. It begins with sdk-.
  5. Keep the production connection server-side. Do not prefix these variables with NEXT_PUBLIC_ in the primary adapter path.
Create .env.local for local development:
The SDK client key identifies a read-only SDK payload endpoint; it is not an administrative API credential. Still, keeping the primary integration server-side prevents targeting rules and unused variations from being sent to the browser. Treat the decryption key, telemetry hash key, webhook secret, Flags Explorer API key, and FLAGS_SECRET as actual secrets. Never expose them through NEXT_PUBLIC_ variables or browser props. Set the staging and production values in the deployment platform rather than committing them. Then verify the environment has a key without printing the key itself:
Expected receipt:
If it prints missing, stop. A silent empty key makes every feature fall back and can conceal a broken production integration.

Create a safe first feature

In GrowthBook, create a feature with these fields:
Feature keys cannot be renamed after creation. Use a semantic key that describes the behavior, not a ticket number or launch date. Review feature key, type, state, and fallback behavior before choosing a key. Enable the feature in dev. Leave staging and production at false until the code is deployed. In each environment, add rules in this order:
  1. Force true when employee is true.
  2. Optionally force true for an explicit test organization.
  3. Roll out true by percentage when identity_ready is true.
  4. Fall through to the feature default, false.
GrowthBook evaluates rules top to bottom, and the first matching rule wins. A percentage rollout should hash on the unit that must stay together:
  • Use organization_id when every member of a B2B account must receive the same checkout.
  • Use user_id when assignments should follow authenticated users across devices.
  • Use anonymous_id when the experience begins before login and must remain stable through that browser journey.
  • Use id only after defining exactly what id represents in the application.
Do not hash on email, session ID, request ID, URL, or a value that changes during the workflow. Read targeting attributes and percentage rollout behavior before publishing the production rule.

Implement the server-first adapter path

Install pinned major/minor versions for the first implementation. A lockfile should capture the exact transitive versions used by CI:
Expected receipt:
Treat the vulnerability line as a package-manager receipt, not a security guarantee. Run the repository’s normal dependency review and security checks.
Current adapter spelling@flags-sdk/growthbook 0.3.1 exports createGrowthbookAdapter, with a lowercase b in book. Older examples use createGrowthBookAdapter. Pin the package and use the spelling exported by that version. If the import fails, inspect the installed package declaration rather than guessing.

Establish an anonymous identity before rendering

Server Components can read cookies, but they cannot set a cookie during render. The first request therefore needs a boundary that creates the anonymous ID, forwards it to the current render, and sets it for later requests. In Next.js 16, create src/proxy.ts:
This Proxy covers pages, Route Handlers, and Server Functions while excluding static files. If the repository already has proxy.ts, merge this logic into it. Next.js permits only 1 Proxy file. If the application requires cookie consent before persistent analytics identifiers, do not set this cookie until consent is established. Use the application’s existing consent and identity system instead. For Next.js 15 and earlier, use the supported middleware.ts convention. The first-request invariant stays the same: the value forwarded to the render and the value written to the cookie must be identical. Verify the cookie without exposing it in application logs:
Expected receipt on the first request:
A second request that sends the cookie should not rotate it. Rotation changes percentage assignments and invalidates experiment joins.

Define the authenticated viewer boundary

Do not make a feature SDK responsible for authentication. Create src/lib/auth/viewer.ts as the adapter between the application’s existing server-side session and feature attributes:
This file is intentionally safe and limited: the anonymous integration works, while authenticated targeting remains unavailable until an engineer connects a trusted session. Never populate it from arbitrary x-user-id or x-plan request headers. A caller can forge those headers unless a trusted ingress strips and replaces them. For an Auth.js application, the replacement has this shape:
Adapt field names to the application’s augmented session type. Derive isEmployee from a trusted role or directory claim, not from an email suffix supplied by the browser.

Resolve one request-scoped attribute object

Create src/lib/feature-flags/attributes.ts:
dedupe ensures the request computes attributes once even if several flags call identify. All flag decisions in that request therefore see the same snapshot. The attribute object excludes email, access tokens, names, raw roles, and the session object. The missing-identity sentinel is not a valid rollout population. Every percentage or experiment rule in this guide includes identity_ready = true. That condition prevents all requests with a broken Proxy or session boundary from sharing 1 deterministic bucket.

Add evaluation and exposure receipts

Feature evaluation and experiment exposure are different events:
  • A feature evaluation occurs whenever code asks for a feature value. A force rule and a percentage rollout do not create experiment exposures.
  • An experiment exposure occurs when an experiment rule assigns a variation and invokes the SDK tracking callback.
Create src/lib/feature-flags/telemetry.ts:
This logging sink is executable and gives you an immediate receipt. It is not automatically an experimentation data pipeline. Before starting an experiment, replace or extend these functions so the events reach the warehouse or analytics source queried by GrowthBook. Preserve the exact assignment identifier required to join exposure and outcome data. If policy prohibits logging the raw hashValue, map it through a stable pseudonymization scheme that the outcome pipeline also uses. Feature diagnostics can query feature-evaluation events after you configure a Feature Usage Query. The Feature Evaluation Diagnostics reference documents the required feature_key and timestamp fields and optional value, rule, and unit metadata.

Define typed flags in one server-only module

Create src/lib/feature-flags/flags.ts:
The server-only import makes an accidental Client Component import fail at build time. The flag fallback is false, which points to code that already works. The adapter initializes lazily and evaluates with the request attributes returned by identify. The 1,200 ms timeout limits cold-start waiting for a feature payload. It is not a universal latency target. Choose a value using your deployment region, observed CDN behavior, and application latency budget. The SDK can use a cached payload when one exists; on a first cold start with no usable payload, the feature returns the fallback.

Pass the server decision into the Client Component

Create src/app/checkout/checkout-client.tsx:
This component is interactive, but it does not need to evaluate the flag. The Server Component owns the decision and passes a serializable Boolean. That eliminates a second payload fetch and a second experiment evaluation. Create src/app/checkout/page.tsx:
Because identify reads cookies and headers, the route is request-dependent. Do not force this page to static rendering. If the surrounding layout uses Cache Components, keep the flag evaluation in the dynamic part of the tree and pass request values into cached functions rather than reading cookies inside a cached scope.

Use the same flag in a Route Handler

The backend must not assume the browser used a particular path. Evaluate the same server flag in the API boundary. Create src/app/api/checkout/config/route.ts:
Use this read-only endpoint as a parity receipt. In a real checkout mutation, evaluate the flag again inside the authenticated POST handler, then validate authorization, inventory, pricing, and payment independently. A feature flag may choose an implementation; it must never grant permission or bypass validation. Start the application:
Then request both surfaces with the same cookie jar:
With the feature disabled or unavailable, the receipts are:
If they disagree, do not roll out. Check Proxy coverage, cookie forwarding, session consistency, SDK keys, and whether one response was cached publicly.

Make identity an explicit product contract

Deterministic hashing only produces stable assignments when the hash input is stable. The SDK cannot repair an identity model that changes halfway through a workflow. The canonical code exposes 4 identifiers for different purposes: Do not create an experiment rule until the team writes down its randomization unit. A checkout experiment that begins before authentication usually needs anonymous_id. A seat-management change should usually use organization_id. A personal settings change may use user_id.

Handle anonymous-to-authenticated transitions deliberately

The example’s id changes at login. That is useful for authenticated targeting, but it can rebucket a user if an experiment hashes on id. Choose 1 of these patterns instead of accepting the transition accidentally:
  1. Hash on anonymous_id for the whole pre-login journey. Preserve the cookie through checkout. Join the eventual conversion to that anonymous ID in the event pipeline.
  2. Hash on user_id only after authentication. Add a rule condition requiring a non-empty user ID, and keep anonymous users on the fallback.
  3. Hash on organization_id for account-level behavior. Require a selected organization and ensure every backend call resolves the same organization from the trusted session.
  4. Use sticky bucketing when the experiment requires assignment continuity across identifier or experiment changes. Sticky bucketing requires an implemented storage service and is a paid-plan capability in GrowthBook Cloud. It is not part of this guide’s free baseline.
Never copy an anonymous assignment into a user profile without considering shared devices. A browser used by 2 accounts can otherwise transfer an experimental state from one user to another. If you persist assignments, include the experiment key, bucket version, chosen identifier type, creation time, and removal policy.

Keep tenant selection consistent

Many B2B applications let one user switch organizations. Resolve organization_id from the same server-side tenant context used for authorization. Do not read it from a query string and trust it as a targeting attribute. For an organization rollout:
  1. Confirm the route’s authorized organization.
  2. Put that ID into organization_id.
  3. Configure the GrowthBook rollout to hash on organization_id.
  4. Add a condition that organization_id is not empty.
  5. Test 2 users in the same organization and 2 organizations with the same user.
The page and the mutation endpoint must resolve the same organization. A UI-only organization assignment is not sufficient because the server still chooses which implementation handles the request.

Treat attribute changes as schema migrations

Define anonymous_id, user_id, organization_id, employee, plan, and identity_ready under SDK Configuration → Attributes with matching data types. Attribute values are evaluated by the SDK; the attribute definitions in GrowthBook provide targeting metadata and authoring controls. Before renaming or changing an attribute:
  • Find every flag and experiment that references it.
  • Find every SDK that sends it.
  • Check case, empty-string, and missing-value behavior.
  • Check the identifier selected by active experiments.
  • Deploy code that sends both old and new attributes.
  • Migrate rules.
  • Wait through the active assignment and cache window.
  • Remove the old attribute only after the old rules and SDKs are gone.
An attribute migration is not complete when the UI saves. It is complete when every running SDK version and rule agrees on the new schema.

Understand what the adapter does at runtime

@flags-sdk/growthbook 0.3.1 creates a shared GrowthBookClient. The flag wrapper supplies request-specific attributes as a UserContext, so attributes do not need to mutate a global singleton. The adapter lazily initializes the client, refreshes its payload, evaluates the feature, and returns the flag’s defaultValue when the result is null. This distinction matters:
  • Feature definition delivery can involve I/O. The adapter must obtain and refresh the SDK payload from GrowthBook’s CDN or an optional Global Config store.
  • Rule evaluation is local after the payload is available. The SDK evaluates targeting conditions and deterministic hashing in the process.
  • Remote evaluation is a different mode. It sends attributes to a remote evaluator and is not the baseline in this guide.
  • Experiment tracking is application I/O. Your callback sends an exposure to an analytics or warehouse pipeline; GrowthBook does not infer that event merely because a rule exists.
Do not convert “local evaluation” into an absolute “no network” claim. A normal flag check does not require a remote decision call, but payload refresh, Global Config, streaming, remote evaluation, and analytics each have their own network behavior.

Use the default adapter when defaults are enough

The custom adapter in this guide validates required configuration and sets observability callbacks. A smaller application can use the default singleton:
The default adapter reads GROWTHBOOK_CLIENT_KEY, GROWTHBOOK_API_HOST, GROWTHBOOK_APP_ORIGIN, and optional Global Config variables. It logs a missing client key rather than enforcing the deployment check used above. Keep an independent environment-variable preflight if a missing key should fail the build or startup.

Do not use one mutable GrowthBook instance per user

An unsafe server implementation often looks like this:
Concurrent requests can overwrite attributes between calls. The adapter avoids that pattern by passing user context into each evaluation. If you use the older GrowthBook class directly on the server, create a request-scoped instance and destroy it after extracting results and deferred tracking data.

Use the direct JavaScript and React SDK path when needed

Use this alternative when the repository does not use the Flags SDK convention, needs complete ownership of Next.js fetch caching, or already has a direct GrowthBook integration. Do not install it alongside the primary adapter for the same flag. The alternative below does 4 things:
  1. Fetches the SDK payload with Next.js Data Cache tags.
  2. Creates a request-scoped server SDK instance.
  3. Evaluates server flags before rendering.
  4. Hydrates a decrypted payload and the same attributes for client-only flags.
If rules or unused variations are sensitive, stop after step 3 and pass resolved values as props. Hydration sends the decrypted payload to the browser. Front-end remote evaluation keeps rules off the client, but the current React SDK documentation does not support remote evaluation in a hybrid SSR/client integration. Choose server evaluation instead of forcing those modes together. Install the direct packages only for this branch:

Define strict feature types

Create src/lib/feature-flags/app-features.ts:
The first feature is server-owned. The second exists only to demonstrate a client-owned decision. Keeping them separate prevents duplicate exposure tracking.

Fetch and initialize on the server

Create src/lib/feature-flags/direct-server.ts:
The explicit fetch endpoint is {apiHost}/api/features/{clientKey}. It is a read-only SDK Connection endpoint. Next.js caches successful responses for up to 300 seconds and associates them with growthbook-features. The fallback is an empty payload, which makes unknown features use their code fallbacks. AbortSignal.timeout() requires the tested Node.js runtime. For an older runtime, use an AbortController timer. Do not omit the bounded failure path on a request-critical page.

Hydrate client-only features without flicker

Create src/lib/feature-flags/direct-provider.tsx:
initSync prevents the first client render from evaluating against an empty payload. It cannot decrypt encrypted payloads, so the server must pass getDecryptedPayload(). That is why this pattern is inappropriate when the payload itself must stay private. The example pushes browser exposures to dataLayer. Confirm that Google Tag Manager, or another consumer of that array, sends the event to the data source queried by GrowthBook. If the application uses Segment, RudderStack, Snowplow, or a custom tracker, replace the callback with that tracker’s established event contract. Create a client-only component, src/app/checkout/client-checkout-help.tsx:
Then use the direct path as a complete alternative src/app/checkout/page.tsx:
The server evaluates new-checkout-flow once. The browser evaluates only client-checkout-help. If both flags become experiments, their exposures come from different tracking callbacks by design.

Choose server or browser tracking, not both

For each experiment, document its evaluation owner:
If the same experiment must be evaluated on both sides, deduplicate by an event key derived from experiment ID, variation ID, assignment identifier, and experiment phase. Do not deduplicate only by page view. A user can legitimately enter separate experiments on one page, and experiment phases can reuse a feature key.

Design feature definition delivery

Feature evaluation correctness depends on which payload a process holds. Choose a delivery strategy instead of combining every cache layer.

Adapter baseline: CDN plus SDK cache

Without Global Config, the adapter initializes its GrowthBookClient from the SDK endpoint and uses the JavaScript SDK’s caching and stale-while-revalidate behavior. The adapter requests a refresh before evaluation. In a long-lived Node.js process, the in-memory cache can serve later requests. In serverless deployments, each warm isolate has its own memory and a new isolate can start without that cache. Use this baseline when:
  • A short propagation window is acceptable.
  • The code fallback is safe on a cold start.
  • The deployment does not require a shared configuration store.
  • The team can observe payload-source failures.
Do not claim a flag has propagated merely because it is published in the GrowthBook UI. Verify an application request from each deployment region or instance class that matters.

Vercel adapter option: Global Config

@flags-sdk/growthbook 0.3.1 supports Vercel Global Config. Set these server variables:
The item key defaults to the GrowthBook client key. Configure an SDK Webhook on the same GrowthBook SDK Connection to update the Global Config item when the payload changes. The adapter’s older Edge Config variables remain deprecated fallbacks, but new integrations should use the Global Config names exported by the installed adapter. This option adds another operational dependency. Verify all of the following:
  • The webhook updates the item tied to the same SDK client key used by the app.
  • The GrowthBook environment and Vercel deployment environment match.
  • The config store size can hold the SDK payload.
  • A missing or malformed item falls back safely.
  • The Vercel API token used by the webhook has only the required scope and has a rotation owner.
The Next.js Flags adapter documentation in the pinned GrowthBook source uses the earlier Edge Config terminology. Check the installed adapter and current Vercel provider documentation when configuring this optional path.

Direct SDK option: Next.js Data Cache plus webhook

The direct implementation tags its payload fetch. Add a signed webhook Route Handler to invalidate that tag. Create src/app/api/internal/growthbook-webhook/route.ts:
Set GROWTHBOOK_WEBHOOK_SECRET to the shared secret shown for that SDK Webhook. In GrowthBook, configure an HTTP Endpoint SDK Webhook with this Route Handler’s HTTPS URL and test it. GrowthBook signs SDK webhooks and retries a failed delivery up to 2 additional times. Read the SDK Webhooks signature and payload reference before changing the verification code. The handler verifies the raw request body before parsing it and rejects messages older than 5 minutes. Record processed webhook-id values in a durable store if duplicate processing would have side effects. Cache invalidation is idempotent, so an in-memory duplicate guard is not required here. For self-hosted Next.js with multiple application instances, confirm the Data Cache and tag invalidation are shared. Default local caches do not automatically coordinate every instance. A webhook that reaches only 1 node can leave other nodes stale until their time-based revalidation. Use a shared cache handler, broadcast invalidation, or rely on a delivery layer designed for the deployment topology.

Streaming is optional, not the default answer

The direct JavaScript and React SDKs can subscribe to server-sent event updates when streaming is enabled. GrowthBook Cloud and GrowthBook Proxy support the documented streaming path. The Flags adapter explicitly initializes with streaming disabled in its current implementation. Use streaming only when the application genuinely needs faster propagation than a webhook or bounded cache window. Account for connection limits, reconnect behavior, serverless lifecycle, background tabs, and self-hosted proxy availability. A kill switch still needs a code fallback because a stream cannot help a process that never obtained a valid initial payload.

Enforce feature keys and values with TypeScript

A fallback gives runtime safety. Strict feature types give change-time safety. Use both. The primary Flags adapter already declares the local flag as flag<boolean, AppAttributes> and growthbookAdapter.feature<boolean>(). A string value cannot reach the component without a TypeScript error. Centralizing exports also makes direct string references easy to find:
Do not export the adapter itself. Application modules should consume named decisions, not construct arbitrary feature keys throughout the codebase. For the direct SDK path, parameterize GrowthBook<AppFeatures> as shown above. Then a typo such as this fails compilation:

Generate types from GrowthBook

For a large feature set, generate AppFeatures rather than maintaining it manually. Install the current GrowthBook CLI and authenticate with a Secret Key or Personal Access Token that can read the intended organization:
Expected files:
Inspect the file before using it. Confirm new-checkout-flow is Boolean and confirm the command read the intended GrowthBook organization and project. Add deterministic scripts to package.json:
In CI, provide the CLI credential as GBCLI_BEARER_AUTH through the secret store. Do not put it in .env.example, a generated file, a test snapshot, or build logs. If CI cannot contact GrowthBook, choose 1 policy explicitly:
  • Block merges because the generated feature contract is authoritative.
  • Run generation in a scheduled job and make normal CI validate the checked-in result.
  • Keep a manually reviewed local interface and skip live generation.
Do not silently regenerate types and commit them from an untrusted pull request. A generated file is still a source change and requires review.

Validate the exact key inventory

Run this search in CI or during review:
The primary adapter path should produce few or no raw method calls because flags are named exports. Search for raw keys too:
Expected call sites are the central flag declaration, fixtures, tests, and migration metadata. A raw key in an unrelated component is a sign that the abstraction boundary is leaking.

Test behavior without a live control plane

Tests should prove evaluation semantics with a fixed payload. They should not depend on whichever rule happens to be published in a shared development environment.

Test the attribute builder

Create src/lib/feature-flags/attributes.test.ts:
This test exercises the pure boundary, not Next.js request APIs. Keep cookie/header integration in an end-to-end test.

Test rules with a fixed SDK payload

Create src/lib/feature-flags/evaluation.test.ts:
This fixture mirrors the intended rule order but remains independent of the GrowthBook UI. Update it deliberately when the rollout architecture changes. It proves that employee targeting wins, missing identity falls back, repeated identity is stable, and the rollout includes both variations.

Test both components

The decision and the rendering branches are separate contracts. Use the repository’s React test runner to render CheckoutClient with useNewFlow={false} and useNewFlow={true}. Assert that the legacy and new test IDs are mutually exclusive. If the repository has no component-test setup, the Playwright test below covers the deployed branch while the fixed SDK test covers both values.

Test page and API parity

Create tests/feature-flag-parity.spec.ts:
Use a Playwright configuration whose baseURL points at a production build under test. context.request shares cookies with that browser context. A standalone request fixture does not represent the same browser session, so do not use it for this parity check.

Run the verification sequence

Add or adapt these scripts:
Run:
Expected receipt:
Do not report the integration as verified if a command was skipped. State which layer remains unverified: types, evaluation semantics, production build, live payload, browser parity, analytics delivery, or rollout propagation.

Observe decisions without corrupting experiments

An application needs 3 separate observability channels:
  1. Payload health: Did initialization use a current payload, a cache, a timeout, or a fallback?
  2. Feature evaluation: Which feature, value, source, and rule did the SDK return?
  3. Experiment exposure: Which experiment assigned which variation to which randomization unit?
Do not combine them into 1 ambiguous flag_used event.

Payload health

The direct path returns source as next-cache-or-network or safe-fallback. Emit a counter for the fallback and alert on a sustained rate, not a single cold-start failure. The adapter’s underlying init() response can report network, cache, init, error, or timeout; if payload-source telemetry is operationally required, wrap initialization during process warm-up or use the direct path where that receipt is explicit. Useful dimensions are deployment environment, application version, region, SDK client-key fingerprint, and payload dateUpdated. Do not log the full payload. It can contain internal flag names, rule structures, and variations.

Feature evaluation

Use onFeatureUsage for diagnostics and code-reference signals. Include:
Sample high-volume events or aggregate them if cost requires it, but keep enough coverage to diagnose a rollout. Never infer exposure counts from feature-usage diagnostics. A feature can be evaluated speculatively without the user seeing the behavior.

Experiment exposure

An exposure event should fire only when an experiment rule assigns a variation at the point of exposure. The adapter’s trackingCallback fires when an experiment-backed feature is evaluated. Structure the component tree so evaluation is close to the actual experience. Do not evaluate all known experiment flags in the root layout “just in case.” That overcounts users who never reach the feature. At minimum, preserve:
The assignment identifier in the exposure table must join to the same unit in the outcome table. A hashed exposure ID cannot join to a raw outcome ID unless the outcome pipeline applies the same hash.

Verify event delivery

For server logging, exercise a known experiment identity and search the logs:
Expected structured lines resemble:
experiment_viewed should not appear for a force or percentage-rollout rule. It should appear only after you replace the rollout with an experiment rule and the identity is included. In the warehouse, run a bounded query adapted to the application’s schema:
The query syntax differs by warehouse. The required receipt is not a particular row count. It is evidence that the test identity appears once under the expected experiment, variation, and unit.

Secure the integration

Feature flags are operational controls, not security controls.

Keep authorization outside the flag

This is unsafe:
This preserves authorization:
A flag may choose 2 authorized implementations. It cannot make an unauthorized user authorized.

Minimize attributes

Send only fields used for targeting or assignment. Prefer opaque application IDs over email. If email-based targeting is unavoidable, configure a secure-string attribute and hash it exactly as documented in the JavaScript SDK secure attributes reference. Secure attribute hashing changes what appears in the SDK payload; it does not remove the need to control where the salt and raw values live. Keep attributes out of broad logs. The telemetry example pseudonymizes the unit ID when a telemetry key is configured. Review the retention period and access policy for that derived identifier.

Decide whether the payload may reach the browser

Local browser evaluation exposes the SDK payload to the browser. Do not put secrets in feature values or targeting rules. Feature flags are configuration, not a secret manager. Use server evaluation when:
  • Rule conditions reveal internal account lists or unreleased product logic.
  • Unused JSON/string variations contain sensitive configuration.
  • The decision protects server behavior.
  • A client could tamper with the rendered choice and the server must remain authoritative.
Front-end remote evaluation can keep rules and unused variations off the client, but it introduces remote evaluation calls when relevant attributes change and has deployment and plan requirements. It is not supported for the hybrid React pattern described earlier. Read remote evaluation requirements before selecting it.

Separate credentials by purpose

Use distinct credentials:
  • GROWTHBOOK_CLIENT_KEY: read-only SDK payload identity for 1 environment.
  • GROWTHBOOK_DECRYPTION_KEY: decrypts encrypted SDK payloads; server-only in this guide.
  • GROWTHBOOK_WEBHOOK_SECRET: verifies SDK webhook requests.
  • GROWTHBOOK_API_KEY: optional read-only metadata access for Flags Explorer; never needed for normal evaluation.
  • FLAGS_SECRET: protects the Vercel Flags discovery/override endpoint when that optional tooling is enabled.
  • GBCLI_BEARER_AUTH: CLI credential for type generation or other reviewed automation.
Never reuse the webhook secret as a telemetry hash key or application session secret. Give each a separate rotation procedure.

Protect the optional Flags Explorer endpoint

Flags Explorer is not required for the implementation. If the team enables it, use createFlagsDiscoveryEndpoint, a read-only GrowthBook API credential, and FLAGS_SECRET as documented in the Next.js adapter Flags Explorer integration. Do not expose an unprotected route that lists internal flag metadata.

Know which controls require a plan

The baseline in this guide does not require an advanced release feature. A GrowthBook Cloud Starter organization can use built-in environments, Boolean flags, advanced targeting, manual percentage rollout, and an instant kill switch. The current Cloud Starter plan is limited by seats, projects, and included CDN usage even though feature flag and traffic counts are not priced per evaluation. Check the current GrowthBook pricing page before promising limits or availability. As verified on August 12, 2026:
  • Cloud Starter baseline: Default environments, unlimited feature flags, manual percentage rollouts, advanced targeting, kill switches, stale flag management, flag history, and standard webhooks.
  • Cloud Pro additions relevant here: Scheduled feature flags, Safe Rollouts with automated rollback, sticky bucketing, code references, encrypted SDK endpoints, and remote evaluation.
  • Cloud Enterprise additions relevant here: Ramp schedules, approval workflows, exportable audit logs, advanced access controls, and other governed release controls.
  • Self-hosted: Open-source and commercial feature availability differs from GrowthBook Cloud. Verify the deployed license and server version rather than copying Cloud assumptions.
The GrowthBook environment documentation says the built-in production, dev, staging, and test environments are available to free organizations. Custom environment packaging has changed over time, so confirm current plan details before creating per-branch or per-region environments. The implementation must remain safe without plan-gated automation. If Safe Rollouts or ramp schedules are unavailable, use manual checkpoints with explicit stop criteria and a named operator.

Roll out from 0 to 1 to 100

A rollout is a sequence of verified state transitions, not a slider movement. Write the release record before exposing customers:
Replace thresholds with values grounded in the application’s baseline. “No obvious errors” is not a stop criterion.

Stage 0: Merge dark code

Before enabling the feature for anyone:
  1. Create the flag with false as its default.
  2. Keep production disabled.
  3. Deploy both implementations.
  4. Confirm a missing payload renders the legacy component and API path.
  5. Confirm the new code does not run hidden side effects while its UI is off.
  6. Run type, unit, build, and parity tests.
  7. Confirm server logs show defaultValue or fallback behavior for a known request.
“Dark” means unreachable by normal users, not merely hidden with CSS. Do not execute a new payment mutation and hide its output.

Stage 1: Enable internal identities

Publish the top-priority employee rule in dev, then staging, then production. Use a trusted employee attribute. Verify:
  • An employee sees the new component and API path.
  • A non-employee with the same plan stays on the legacy path.
  • The same identity receives the same value after navigation and refresh.
  • The new path passes functional checkout tests.
  • The kill switch returns the employee to the legacy path.
  • Feature-evaluation receipts contain the intended ruleId.
If production rules take longer than the propagation objective, stop and debug delivery before exposing customers. Do not compensate by repeatedly publishing the same change.

Stage 2: Enable a test organization

For B2B checkout, target 1 opt-in organization before percentage rollout. This reveals tenant-context errors that employee targeting may miss. Confirm that 2 users in the organization receive the same result and a user who switches to another organization receives that organization’s result. For a consumer application, use an explicit beta cohort or a small allowlist of durable IDs. Keep allowlists small; large ID lists expand the SDK payload and become hard to govern.

Stage 3: Start a manual percentage rollout

Add a percentage rule below the internal and test-account rules:
If anonymous or user assignment is intended, change only the hash attribute. Do not switch the hash attribute after rollout starts. A switch changes the bucket population and invalidates comparison across stages. At 1%, wait long enough to observe the application’s normal traffic cycle and delayed errors. Check:
  • Payload fallback and initialization errors.
  • Checkout page and API parity.
  • Server error and timeout rates by feature value.
  • Payment or order-creation errors by feature value.
  • Support reports and client exceptions.
  • Assignment balance as a diagnostic, if the population is large enough.
  • The count of missing identities.
A manual percentage rollout is a release control, not automatically an A/B test. Do not calculate a causal lift from sampled application logs unless you created an experiment rule, recorded valid exposures, joined outcomes on the randomization unit, and followed an analysis plan.

Stage 4: Expand through checkpoints

A default manual schedule is:
The percentages are not universal. Increase the first cohort when traffic is low enough that 1% produces no signal. Add regional or account-tier checkpoints when the failure domain demands them. At every transition:
  1. Record the old and new coverage.
  2. Record who published it.
  3. Record the observation start and earliest next decision time.
  4. Verify the published payload reaches the application.
  5. Compare operational signals against predeclared stop thresholds.
  6. Exercise a known legacy and new identity.
  7. Either advance, hold, or roll back.
Do not change coverage, rule order, hash attribute, variation meaning, and application code at the same time. Multiple simultaneous changes make a regression hard to attribute.

Stage 5: Reach 100% without deleting the fallback

At 100%, every eligible identity receives the new value, but the release is not finished. Keep the legacy path during a bounded rollback window. Verify:
  • All intended populations are eligible; empty identifiers are not silently falling through.
  • Disabled environments still use the code fallback.
  • A top-priority force-false rule returns a test identity to legacy.
  • The production process has received the 100% payload in every relevant region.
  • Background workers and Route Handlers that use the flag agree with the page.
  • No active experiment still depends on the old variation.
Set the cleanup decision date while the rollout context is fresh. A flag at 100% is not maintenance-free. It preserves 2 branches, 2 test surfaces, and a mutable production decision.

Rollback: run the drill before it is needed

The safe rollback for this guide is a control-plane change, followed by an application receipt.

Roll back a normal rollout

  1. Open new-checkout-flow in GrowthBook.
  2. Draft a production change that forces false above every other rule, or disable the production feature environment.
  3. Publish the change using the organization’s normal review policy.
  4. Wait no longer than the stated propagation objective.
  5. Request /checkout and /api/checkout/config with the same known identity.
  6. Confirm the page says off, the API says legacy, and new checkouts use the old implementation.
  7. Confirm error signals recover.
  8. Leave the rollback rule and incident evidence in place until the cause is understood.
If the optional signed webhook backs the direct cache path, confirm the webhook returns HTTP 200 and the next request obtains a fresh payload. If Global Config backs the adapter, confirm its item changed. If neither receipt exists, a UI publish is not proof that the application changed.

Roll back when GrowthBook is unavailable

If the control plane is unavailable but the application has a cached true payload, a code fallback alone does not override that cached value. Plan an application-level emergency mechanism for changes whose risk requires independence from the flag delivery plane. Options include:
  • A server-only environment variable such as FORCE_LEGACY_CHECKOUT=true, read before the SDK decision and changed through the deployment platform.
  • An operational configuration store already used for emergency controls.
  • A deployment that changes the code default and bypasses the new path.
If you add an emergency override, keep it narrow and observable:
Call shouldUseNewCheckout() from both page and API instead of calling the raw flag. Document who can set the variable, how long a deployment change takes, and how the override is removed. Do not create a generic unsigned header that forces arbitrary flags.

Do not use rollback to reverse incompatible data

The legacy code path must remain compatible with state written by the new path. Use expand-and-contract migrations:
  1. Deploy schema changes that both versions can read.
  2. Write backward-compatible data.
  3. Roll out the new behavior.
  4. Wait through the rollback window.
  5. Migrate or backfill data.
  6. Remove legacy reads only after rollback is no longer required.
A feature flag can select code. It cannot make an irreversible database write reversible.

Use the failure matrix during review

Review these cases before production: The failure state must be visible. “Falls back safely” without a counter or log turns a broken integration into a long-lived silent release failure.

Know when DIY is the better answer

GrowthBook is not necessary for every Boolean. Keep a deployment-time environment variable when all of these are true:
  • The value changes only through a normal deployment.
  • No user, account, attribute, or percentage targeting is required.
  • The application has 1 environment or environment differences already map cleanly to deployment configuration.
  • No non-developer needs to operate the value.
  • No experiment will use the decision.
  • No audit, approval, usage diagnostic, or stale-flag workflow is required.
  • A code deployment is an acceptable rollback mechanism.
  • The configuration is expected to remain permanent rather than become temporary release debt.
For example, ENABLE_VERBOSE_LOCAL_LOGS in a developer-only build may be a valid environment variable. An entitlement such as CAN_EXPORT_BILLING_DATA should remain in the permission system, not GrowthBook.

The DIY threshold

A homegrown Boolean usually evolves through this sequence:
DIY is still reasonable when the team deliberately needs only the first 1 or 2 stages. It becomes an internal platform when several later stages are requirements. GrowthBook’s value in this task is not that it computes if (enabled). The application still owns both branches. GrowthBook supplies a shared feature model, environment-specific payloads, targeting rules, deterministic assignment, an operator interface, integration points for experiments, and lifecycle metadata. The team still owns identity correctness, authorization, analytics delivery, application compatibility, incident response, and code removal.

Compare the maintenance obligation

Use this decision record: Do not select GrowthBook only to avoid writing 1 conditional. Select it when the release and measurement lifecycle would otherwise become internal infrastructure.

Remove the flag after the release

The terminal state is not “100% on.” It is 1 implementation with no temporary decision.

Establish removal criteria at creation

Record:
Flag history and stale detection can identify candidates, but they cannot prove that deleting a branch is safe. An engineer must review side effects, tests, background jobs, and data compatibility.

Remove the losing branch in order

After the rollback window:
  1. Confirm no active experiment, ramp, or incident depends on the flag.
  2. Confirm production has been stable at true for the agreed period.
  3. Change code so the new checkout is unconditional.
  4. Remove the legacy component, API implementation, imports, tests, and telemetry dimensions used only by the split.
  5. Deploy the unconditional code while the GrowthBook flag still exists.
  6. Verify checkout behavior and health.
  7. Archive the GrowthBook feature.
  8. Wait through the maximum old-application deployment window.
  9. Delete the feature only if organizational retention policy allows it.
  10. Remove the key from generated types and fixtures.
Deploying unconditional code before archiving the control ensures an older running instance can still evaluate the known key while the new instances no longer depend on it. Search for residue:
Expected receipt after cleanup:
Then regenerate types and run the full verification sequence. If a background worker or mobile client still uses the feature, the key is not ready for deletion even if the Next.js repository is clean.

Definition of done

The integration is complete only when every applicable statement is true.

Architecture and identity

  • The repository inspection identifies App Router, runtime, auth boundary, analytics path, hosting topology, and safe fallback.
  • Server-owned decisions evaluate on the server and reach Client Components as resolved values.
  • The page and Route Handler use the same request identity and organization context.
  • The anonymous ID is durable, consent-compatible, and available on the first render.
  • Percentage rules and experiments exclude missing identities.
  • The randomization unit is documented and does not change mid-rollout.

Delivery and failure behavior

  • Development, staging, and production use SDK Connections tied to the correct GrowthBook environments.
  • No server credential is exposed through NEXT_PUBLIC_ variables or browser props.
  • Initialization has a bounded timeout and a known-safe false fallback.
  • The deployed cache strategy has a measured propagation objective.
  • Webhook or Global Config delivery, when used, has a successful live receipt.
  • Multi-instance cache invalidation is coordinated or explicitly bounded by time.
  • An independent emergency rollback exists if the change’s risk requires it.

Tests and observability

  • TypeScript rejects an unknown feature key and wrong feature value type.
  • Unit tests cover employee targeting, missing identity, deterministic assignment, both rollout sides, and unknown-feature fallback.
  • The production build succeeds.
  • Playwright proves the page and API agree before and after refresh.
  • Feature evaluation and experiment exposure use separate event contracts.
  • An experiment exposure reaches the actual analytics or warehouse destination before an experiment starts.
  • Logs and metrics can distinguish a current payload from a fallback or stale payload.

Operations and lifecycle

  • Internal targeting succeeds before customer exposure.
  • Every percentage checkpoint has named stop conditions and a release record.
  • A rollback drill forces the legacy page and API path within the stated objective.
  • Both code paths remain data-compatible through the rollback window.
  • The feature has an owner, cleanup issue, and removal deadline.
  • The final state removes the legacy branch, the flag check, fixtures, generated type, and feature after old deployments no longer depend on it.
Run the final local receipt:
Then capture 3 live receipts from staging:
If the team cannot produce all 3, the integration has reached “code merged,” not “production-ready feature flags.”

Source map and freshness contract

This guide was verified on August 12, 2026 against GrowthBook commit e44a15af063860c7118f52508746356d55e5a91d, the released package versions in tested_stack, and current Next.js 16 documentation. Primary GrowthBook source paths used: External primary sources used: Reverify this guide when any dependency in this manifest changes:
When a merged pull request touches a dependency, update the executable fixture first. Run the full verification list. Then change this guide. A source edit is evidence that the page may be stale; it is not evidence that its behavior changed.