The @flagsync/nextjs-sdk integrates into Next.js applications with Vercel Flags for feature management and event tracking—ideal for SSR and static site generation workflows.
Call your feature flag like an async function in a Server Component to get the evaluated value.
app/page.tsx
import { myFlag } from '@/lib/flagsync/flags';export default async function Page() { const value = await killswitchFlag(); return <div>{killswitchFlag.key} is {value}</div>;}
Ensure the key in FsUserContext is unique and persistent for accurate MAU tracking and consistent flag evaluations. See User Context Best Practices for details.
1
Create a Helper Function
Create identify with the createIdentify helper, linking flags to user contexts.
Create a helper function to construct the user context from request cookies, headers, or your application’s auth system.Adjust the file below to meet your needs.
@/lib/flagsync/user-context
import type { ReadonlyHeaders, ReadonlyRequestCookies } from 'flags';import type { NextRequest } from 'next/server';type FlagParamsType = { cookies: ReadonlyRequestCookies | NextRequest['cookies'], headers: ReadonlyHeaders | NextRequest['headers'],};// Return the user context (FsUserContext)export async function getFlagSyncUserContext( params: FlagParamsType): FsUserContext { const userId = cookies.get('user-id')?.value; const visitorId = cookies.get('visitor-id')?.value; return { key: userId ?? visitorId, attributes: { region: params.headers.get('x-region'), userAgent: params.headers.get('user-agent') } };};
The key is set using a persistent userId or visitorId from cookies, falling back to a generated ID with nanoid(). For proper MAU tracking and consistent flag evaluations, ensure this key is unique and persistent across requests—see User Context Best Practices.
3
Set Cookies in Middleware (Optional)
Use middleware to set user identification cookies, simplifying context retrieval in getFlagSyncUserContext.
middleware.ts
import { jwtVerify } from 'jose';import { NextRequest, NextResponse } from 'next/server';import { nanoid } from 'nanoid';const jwtSecret = new TextEncoder().encode('your-jwt-secret-key');export async function middleware(request: NextRequest) { const response = NextResponse.next(); const jwt = request.cookies.get('jwt')?.value; // Replace this with your own logic to identify the user let user; if (jwt) { const { payload } = await jwtVerify(jwt, jwtSecret); user = payload; } if (user?.userId) { // Set the user-id cookie response.cookies.set('user-id', user.userId); } else { // Set the visitor-id cookie const visitorId = request.cookies.get('visitor-id')?.value response.cookies.set('visitor-id', visitorId ?? nanoid()); } return response;}
4
Use the Helper in Flag Definitions
Pass identify to the flag definition. This connects flag evaluation to the user context.
@/lib/flagsync/flags
import { flag } from 'flags/next';import { adapter, identify } from '@/lib/flagsync';export const killswitchFlag = flag({ key: 'my-first-kill-switch', adapter, identify,});
The are two distinct approaches for evaluating flags, each with different setup requirements and type-safety guarantees.
With the FlagSync CLI (Recommended): This approach provides maximum type safety. It automatically generates types that validate your flag keys and infer their specific return values.
Manual Typing: If you aren’t using the CLI, this method still provides strong type safety for a flag’s return value, but requires you to create and manage type-specific adapters manually.
When using the CLI, you must use the createTypedFlag function from the @flagsync/nextjs-sdk to build a type-safe flag function.This approach provides full type safety: from key validation to inferred return values, which isn’t possible with the native flags/next package as it’s unaware of your custom types.
1
Generate Types
First, run the FlagSync CLI to generate a flags.d.ts file, mapping your flag keys to their specific data types.
gen/flags.d.ts
// THIS FILE WAS GENERATED BY FLAGSYNC-CLIimport { FeatureFlags } from "@flagsync/nextjs-sdk";declare module "@flagsync/nextjs-sdk" { export interface FeatureFlags { "my-first-kill-switch": boolean; "price-discount": 0.1 | 0.2 | 0.3; }}
2
Build the Type-Safe Flag Function
Create and export a flag function with the createTypedFlag factory.
@lib/flagsync
import { createClient, createIdentify, createTypedFlag,} from '@flagsync/nextjs-sdk';import { getFlagSyncUserContext } from '@/lib/flagsync/user-context';const client = createClient({ sdkKey: process.env.FLAGSYNC_SDK_KEY!});export const { flag } = createTypedFlag(client);export const identify = createIdentify(getFlagSyncUserContext);
3
Define Your Flags
Now you can define all your flags. Thanks to the generated types, you’ll get full autocompletion for keys, and TypeScript will automatically infer the correct return type for each flag.
@lib/flagsync/flags
import { flag, identify } from '@/lib/flagsync';// The return value of killswitchFlag() is now Promise<boolean>.export const killswitchFlag = flag({ identify, key: 'my-first-kill-switch',});// The return value of discountFlag() is now Promise<0.1 | 0.2 | 0.3>export const discountFlag = flag({ identify, key: 'price-discount',});
There’s no need for an adapter when using the typed flag function.
4
Use in a Server Component
Await the flag definition in Server Components:
app/page.tsx
import { killswitchFlag } from '@/lib/flagsync/flags';export default async function Page() { const value = await killswitchFlag(); // Promise<boolean> return ( <div>The value of {killswitchFlag.key} is {value}</div> );}
An Impression is automatically registered when you evaluate the flag.
If you’re not using the FlagSync CLI, you can still achieve type safety on your flag’s return values. This approach requires you to manually specify each flag’s expected type by creating and using type-specific adapters.You’ll use the native flag function from flags/next and provide it with an adapter that has been pre-typed.
1
Use the AdapterFactory
Create an adapter for each data type you need. This ensures that the return value of your flag function is correctly typed.
@lib/flagsync
import {createClient,createIdentify,createAdapter, // Renamed from createTypedFlag for clarity} from '@flagsync/nextjs-sdk';import { getContext } from '@/lib/flagsync/user-context';export const client = createClient({sdkKey: process.env.FLAGSYNC_SDK_KEY!,});// The factory creates adaptersconst adapterFactory = createAdapter(client);// Create an adapter for each data type you needexport const boolAdapter = adapterFactory<boolean>();export const stringAdapter = adapterFactory<string>();export const numberAdapter = adapterFactory<number>();export const jsonAdapter = adapterFactory<{ foo: string }>();export const identify = createIdentify(getContext);
2
Use the Correct Adapter
When defining your flags, import the native flag function from flags/next and pass the corresponding typed adapter you created. This ensures the flag’s return value and defaultValue are correctly typed.
@lib/flagsync/flags
import { flag } from 'flags/next';import { boolAdapter, numberAdapter, identify,} from '@/lib/flagsync';// Use the boolAdapter for boolean flags.// The return value of killswitchFlag() is now Promise<boolean>.export const killswitchFlag = flag({ identify, key: 'my-first-kill-switch', adapter: boolAdapter, defaultValue: false, // This must be a boolean});// Use the numberAdapter for number-based flags.// The return value of discountFlag() is now Promise<number>.export const discountFlag = flag({ identify, key: 'price-discount', adapter: numberAdapter, defaultValue: 0.1, // This must be a number});
3
Use in a Server Component
Await the flag definition in Server Components:
app/page.tsx
import { killswitchFlag } from '@/lib/flagsync/flags';export default async function Page() { const value = await killswitchFlag(); // Promise<boolean> return ( <div>The value of {killswitchFlag.key} is {value}</div> );}
Ensure the key in FsUserContext is unique and persistent for accurate MAU tracking and consistent flag evaluations. See User Context Best Practices for details.