Vercel Flags SDK with Next.js 16: Feature Flags, A/B Testing, and Rollout Patterns (2026)

Server-side feature flags in Next.js 16 without client bundles or flash-of-wrong-variant. Walk through install, Edge Config storage, precomputation for static routes, percentage rollouts, kill switches, and how the SDK compares to LaunchDarkly, Statsig, and GrowthBook.

Vercel Flags SDK for Next.js 16 (2026)

Updated: August 29, 2026

The Vercel Flags SDK is a framework-aware feature flag library that lets Next.js 16 evaluate flags on the server, in proxy.ts, and inside React Server Components without leaking bundles to the client. You install flags, declare each flag as a function that returns a value, and call it wherever you need it. The SDK handles caching, deduping across a request, and hooking into the Vercel Toolbar for overrides. I’ve moved three production apps onto it, and it replaces roughly 80% of what a paid flag vendor gave us (without the invoice).

  • The Vercel Flags SDK ships as the flags npm package with framework adapters for Next.js, SvelteKit, and Nuxt; the Next.js adapter targets App Router and works in Server Components, Route Handlers, Server Actions, and proxy.ts.
  • Every flag is a typed function created by flag(). The SDK dedupes calls per request, so evaluating the same flag from a layout and a page runs the decide function once.
  • Edge Config is the recommended storage backend because reads are single-digit milliseconds at the edge, and it’s bundled into Vercel projects for free up to 8 KB per store.
  • For static routes, precompute() generates a deterministic code per variant combination so ISR and PPR can cache each cohort separately without turning the page dynamic.
  • Provider adapters exist for LaunchDarkly, Statsig, GrowthBook, Split, Hypertune, and Optimizely, so the SDK does not lock you into Vercel’s own flag store.
  • The Flags Discovery Endpoint (/.well-known/vercel/flags) is what makes the Vercel Toolbar list and override flags in preview deployments. Do not skip it.

What is the Vercel Flags SDK?

The Vercel Flags SDK is an open-source TypeScript library, published as flags on npm, that gives web frameworks a first-class API for feature flags. Vercel donated the reference site to flags-sdk.dev in 2025, and the package is now maintained alongside the Next.js repo. It is provider-agnostic: the SDK itself only defines how a flag is declared, evaluated, and reported to the toolbar. Where the flag values come from (Edge Config, LaunchDarkly, Statsig, a Postgres table, or a hard-coded object) is a swappable adapter.

What makes it interesting for staff-eng readers is the runtime story. Flags are evaluated on the server. There’s no client-side script that phones home, no flash of the wrong variant, no bundle bloat from a giant SDK. In a Server Component you call myFlag() the same way you’d call any async function, and the returned value is a plain string, boolean, or object. The SDK caches the result inside React’s request memoization, so evaluating the same flag from a nested layout costs you nothing.

The other differentiator is precomputation. Traditional flag systems make every page dynamic because you can’t cache a page whose HTML depends on user identity. The Flags SDK’s precompute() helper turns the set of active variants into a short hex code that you append to the URL in proxy.ts, letting ISR and Partial Prerendering keep serving cached HTML per cohort.

Installing and configuring flags in Next.js 16

The install is a single package plus one environment variable. I’ll assume you’re on Next.js 16 with the App Router; the SDK also supports 14 and 15, but the proxy.ts integration below needs 16.

pnpm add flags
# For Edge Config storage:
pnpm add @vercel/edge-config

The one required env var is FLAGS_SECRET, a 32-byte base64 string the SDK uses to sign the encrypted payload it sends to the Vercel Toolbar. Generate it once and add it to .env.local, then to your Vercel project settings.

node -e "console.log(crypto.randomBytes(32).toString('base64url'))"
# paste into .env.local
FLAGS_SECRET=…

Create flags.ts at the project root. Every flag is a call to flag() that returns a typed function. Keep the file small and boring. The point is that rg "flag(" tells you every live flag in the codebase.

// flags.ts
import { flag } from 'flags/next';

export const newCheckoutFlag = flag<boolean>({
  key: 'new-checkout',
  description: 'Enables the redesigned checkout flow',
  defaultValue: false,
  decide: async () => {
    // Replace with an adapter call in the next section.
    return false;
  },
});

export const pricingVariantFlag = flag<'control' | 'anchored' | 'discount'>({
  key: 'pricing-variant',
  description: 'A/B/C test for the pricing page',
  defaultValue: 'control',
  options: ['control', 'anchored', 'discount'],
  decide: async () => 'control',
});

Then wire up the Flags Discovery Endpoint so the Vercel Toolbar can list your flags on preview deployments. This is a one-line route handler; skip it and overrides silently fail.

// app/.well-known/vercel/flags/route.ts
import { verifyAccess, type ApiData } from 'flags';
import { getProviderData } from 'flags/next';
import { NextResponse, type NextRequest } from 'next/server';
import * as flags from '@/flags';

export async function GET(request: NextRequest) {
  const access = await verifyAccess(request.headers.get('Authorization'));
  if (!access) return NextResponse.json(null, { status: 401 });

  const providerData: ApiData = getProviderData(flags);
  return NextResponse.json(providerData);
}

Deploy once, open a preview URL, and you’ll see the flags listed in the Vercel Toolbar with per-flag overrides that persist in an encrypted cookie for your session only. This is the workflow I hand to product managers. They can toggle variants on preview URLs without touching my code or the flag store, which honestly cut about half of the “can you turn X on for me real quick” Slacks I used to get.

How do feature flags work in Server Components?

A flag is a function. In a Server Component, you call it, await the result, and render accordingly. No context provider, no hook, no hydration boundary. The SDK memoizes the decide function per request using React’s cache(), so calling newCheckoutFlag() from three nested components hits your Edge Config exactly once.

// app/checkout/page.tsx
import { newCheckoutFlag } from '@/flags';
import LegacyCheckout from './legacy-checkout';
import NewCheckout from './new-checkout';

export default async function CheckoutPage() {
  const useNew = await newCheckoutFlag();
  return useNew ? <NewCheckout /> : <LegacyCheckout />;
}

For Client Components, you have two choices. Read the flag in the nearest Server Component parent and pass the resolved value down as a prop. That’s what I do 90% of the time because it keeps the client bundle clean. If you genuinely need the value in a deeply-nested client tree, expose it through headers() or a Server Action, but don’t reach for a client-side SDK just to avoid drilling a prop.

Server Actions and Route Handlers work identically. Because flag() returns a plain async function, there’s nothing framework-specific about calling it. Use it wherever you’d use any other side-effectful helper.

Using Edge Config as the flag store

Edge Config is Vercel’s replicated read-only key-value store designed for exactly this use case: config that’s read on every request but changes rarely. Reads are pulled from a per-region cache maintained by the edge network and typically resolve in single-digit milliseconds, which is why the Flags SDK ships a first-class adapter for it.

Provision an Edge Config store from the Vercel dashboard (Storage → Create → Edge Config), then wire the adapter into your flag declarations.

// flags.ts
import { flag } from 'flags/next';
import { createEdgeConfigAdapter } from '@flags-sdk/edge-config';

const edgeConfig = createEdgeConfigAdapter(process.env.EDGE_CONFIG!);

export const newCheckoutFlag = flag<boolean>({
  key: 'new-checkout',
  description: 'Enables the redesigned checkout flow',
  defaultValue: false,
  adapter: edgeConfig(),
});

The stored payload is a JSON object keyed by flag name. The adapter reads flags.new-checkout from the Edge Config item and returns it. Update the value in the Vercel dashboard or with the @vercel/edge-config SDK, and every server region picks it up within a few seconds without a redeploy.

If you need percentage rollouts, evaluate them inside the decide function using the visitor’s stable identifier, usually a cookie you set in proxy.ts. I cover the pattern in the Next.js middleware and proxy guide. The SDK exposes an identify option that receives the request headers and returns an object you can use inside decide, which keeps the decide function pure and testable.

A/B testing with precomputation and static routes

The classic problem with flags in a JAM stack: reading cookies inside a page turns the page dynamic, and dynamic pages don’t benefit from ISR or the static shell in Partial Prerendering. Precomputation is the escape hatch.

The idea is that instead of evaluating flags inside the page, you evaluate them once in proxy.ts and encode the result as a short hex code appended to the URL. The router then rewrites to /pricing/<code>, and Next.js sees each cohort as a distinct static path that can be generated at build time or on demand via generateStaticParams().

// proxy.ts
import { NextResponse, type NextRequest } from 'next/server';
import { precompute } from 'flags/next';
import { pricingVariantFlag } from './flags';

const marketingFlags = [pricingVariantFlag];

export async function middleware(request: NextRequest) {
  if (request.nextUrl.pathname !== '/pricing') return NextResponse.next();

  const code = await precompute(marketingFlags);
  const url = new URL(`/pricing/${code}${request.nextUrl.search}`, request.url);
  return NextResponse.rewrite(url, { request });
}

export const config = { matcher: '/pricing' };
// app/pricing/[code]/page.tsx
import { pricingVariantFlag } from '@/flags';

const marketingFlags = [pricingVariantFlag];

export default async function Page({
  params,
}: {
  params: Promise<{ code: string }>;
}) {
  const { code } = await params;
  const variant = await pricingVariantFlag(code, marketingFlags);
  return <PricingHero variant={variant} />;
}

The route now has three physical URLs, one per variant, and each one is a fully static page that ISR or PPR can cache independently. The user never sees the code in their browser because proxy.ts uses rewrite, not redirect. This is the pattern I run for our marketing site’s pricing page, and it holds a 99+ Lighthouse score while running a three-way price test.

For details on how PPR interacts with cached segments, see the Next.js 16 Cache Components guide.

Rollout patterns: percentages, cohorts, and kill switches

The SDK doesn’t bake rollout logic in. It gives you a place to put it. Here are the three patterns that cover 95% of what I need in production.

Percentage rollout with a stable visitor ID

Set a first-party visitor_id cookie in proxy.ts if one doesn’t exist, then hash it inside the decide function and bucket the visitor into a 0–99 range.

import { flag } from 'flags/next';
import { cookies } from 'next/headers';

async function bucket(visitorId: string): Promise<number> {
  const buf = new TextEncoder().encode(visitorId);
  const hash = await crypto.subtle.digest('SHA-256', buf);
  return new DataView(hash).getUint32(0) % 100;
}

export const newCheckoutFlag = flag<boolean>({
  key: 'new-checkout',
  defaultValue: false,
  identify: async () => ({
    visitorId: (await cookies()).get('visitor_id')?.value ?? 'anon',
  }),
  decide: async ({ entities }) => {
    const rolloutPct = 25; // read this from Edge Config in real code
    return (await bucket(entities.visitorId)) < rolloutPct;
  },
});

Cohort targeting

Any information you can read on the server can gate a flag: plan tier from the session, geo from request.geo, user role from the JWT. Put the read in identify and the decision in decide. Keep them separate, because identify runs once per request and its output is what gets sent to the toolbar for debugging.

Kill switches

Every risky flag gets a boolean sibling I call <flag>-kill. If the kill flag is on, the decide function returns the safe default regardless of the rollout percentage or cohort. I learned this the hard way after a rollout percentage change in Edge Config took ~15 seconds to propagate to every region, which was 15 seconds too many at 3 AM. A dedicated kill flag lets me flip one boolean and reason about exactly what happens.

Vercel Flags SDK vs LaunchDarkly, Statsig, and GrowthBook

The SDK isn’t a competitor to the paid vendors. It’s a shim that fits over them. You can point the Flags SDK adapter at LaunchDarkly, Statsig, GrowthBook, Hypertune, or Split, and get server-side evaluation with the same flag() declaration syntax. What varies is what the vendor gives you on top of raw flag storage: analytics, experiment stats, audit logs, and RBAC.

Feature Flags SDK + Edge Config LaunchDarkly Statsig GrowthBook
Server-side eval in Next.js Native Via adapter Via adapter Via adapter
Edge/proxy.ts support Yes Yes Yes Yes
Storage cost (small team) Free (Edge Config) ~$0 up to 1k MAU, then paid Free tier: 1M events/mo Self-host free; cloud paid
Experiment stats Bring your own Included Included (strong) Included
Audit log / RBAC Vercel roles only Included Included Included
Vendor lock-in risk Low (open-source SDK) High Medium Low (Apache 2.0)
Best for Vercel-hosted apps, dev-owned flags Enterprise with compliance needs Product teams doing heavy A/B testing Teams wanting self-host + Bayesian stats

My rule of thumb: if flags live inside engineering and the decide logic is code, use the SDK against Edge Config and skip the vendor bill. The moment non-engineering stakeholders need audit trails, approval workflows, or experiment result dashboards, add a vendor and keep the SDK as the abstraction layer. That way you don’t rewrite your call sites when you switch vendors, only the adapter changes.

Pitfalls I have hit shipping flags at scale

Six things caught me in the first three months of running the SDK in production. If you skim nothing else, skim this list.

  1. Missing Flags Discovery Endpoint. The toolbar silently doesn’t show your flags. There is no error. Verify by hitting /.well-known/vercel/flags in a preview and confirming you get JSON.
  2. Flag files imported by client components. If flags.ts imports server-only code (like cookies), importing it from a Client Component throws at build time in Turbopack. Split into flags.server.ts and flags.client.ts, or add 'server-only' to the top of the flag module.
  3. Precompute + auth cookies. If your identify function reads an auth cookie and the cookie value has high cardinality (a user ID rather than a bucket), precompute generates a unique code per user and blows out your ISR cache. Bucket first, precompute the bucket. (I hit this exact bug shipping a personalization test last spring; the CDN hit-rate dropped from 92% to about 4% overnight and it took a full afternoon to trace back.)
  4. Forgetting defaultValue. If the Edge Config item is missing the key and you didn’t set defaultValue, the flag returns undefined. Ternary with an undefined boolean surprises people.
  5. Overusing runtime flags in monorepos. In a Turborepo, if you evaluate the same flag in ten packages via ten different helper functions, you lose the request-scoped memoization. Import the flag from a single shared package (@repo/flags) and re-export. This is exactly the pattern the Turborepo shared-packages guide recommends for other cross-cutting utilities.
  6. Runtime mismatch. If your flag adapter uses Node APIs but proxy.ts runs in the edge runtime, evaluations from proxy.ts will crash. Pick edge-compatible adapters (Edge Config is one) or move flag reads out of proxy.ts. Read Edge Runtime vs Node Runtime for the compatibility matrix.

Frequently Asked Questions

Is the Vercel Flags SDK free?

Yes. The flags npm package is MIT-licensed and free to use with any hosting provider. If you use Edge Config as the storage backend on Vercel, the free tier includes 8 KB per store and 15 stores per account, which fits dozens of flags. Third-party adapters (LaunchDarkly, Statsig, etc.) follow their own pricing.

Do I need Vercel hosting to use the Flags SDK?

No. The SDK itself is provider-neutral and runs on any Node.js host. You lose the built-in Vercel Toolbar integration and Edge Config, but you can still declare flags, evaluate them server-side, and point the adapter at LaunchDarkly, Statsig, a database, or an in-memory object.

Can I evaluate feature flags in proxy.ts (middleware)?

Yes, and this is the recommended pattern for URL-level rewrites and precomputation. Use edge-runtime-compatible adapters; Edge Config is the safest choice. Avoid adapters that require Node APIs (like the file system) because proxy.ts runs on the edge runtime by default in Next.js 16.

How is the Flags SDK different from a boolean in an env var?

An env var requires a redeploy to change, applies to every user identically, and has no audit trail. The Flags SDK evaluates per-request based on identity, updates in seconds without a redeploy via Edge Config or your vendor, and integrates with the Vercel Toolbar for per-user overrides during QA.

Does the Flags SDK work with Partial Prerendering?

Yes, when combined with precompute(). Precomputation encodes each variant combination as a URL segment so Next.js can generate and cache one static shell per cohort. Without precomputation, calling a flag inside a page turns that page fully dynamic and disables PPR’s static shell for the affected route.

Mei-Lin Wu
About the Author Mei-Lin Wu

Front-end architect at a SaaS. Owns the build system, the design system, and the war stories about both.