Feature flags in Next.js
A feature flag lets you ship a Next.js page now and decide later who sees the new version. This guide adds a flag to a Next.js app that uses the App Router, with one small module and one client component.
The examples use Flagbearer's browser SDK. There's a complete app to copy at flagbearer-dev/nextjs-example.
Install
npm install @flagbearer/webAdd your client key
Client keys start with fbc_ and are public, so they're safe in browser code.
Put yours in .env.local. The NEXT_PUBLIC_ prefix is what lets Next.js send
it to the browser:
NEXT_PUBLIC_FLAGBEARER_CLIENT_KEY=fbc_...One client and two hooks
Create the client once, in its own module, and read flags with React's
useSyncExternalStore. That way a component re-renders when a flag changes:
// app/flags.ts
import { useEffect, useSyncExternalStore } from "react";
import { init, type FlagKey } from "@flagbearer/web";
export const flags = init({
clientKey: process.env.NEXT_PUBLIC_FLAGBEARER_CLIENT_KEY!,
});
export function useFlag(key: FlagKey, defaultValue: boolean) {
const enabled = useSyncExternalStore(
flags.subscribe,
() => flags.getSnapshot()[key] ?? defaultValue,
() => defaultValue,
);
useEffect(() => {
flags.isEnabled(key, defaultValue);
}, [key, defaultValue, enabled]);
return enabled;
}
// False until the current visitor's flags have loaded.
export function useFlagsLoaded() {
return useSyncExternalStore(
flags.subscribe,
() => flags.getStatus() !== "loading",
() => false,
);
}Check the flag in a client component
Hooks run in client components, so the component that checks the flag starts
with "use client":
// app/checkout.tsx
"use client";
import { useFlag, useFlagsLoaded } from "./flags";
export function Checkout() {
const loaded = useFlagsLoaded();
const newCheckout = useFlag("new-checkout", false);
if (!loaded) return <p>Loading checkout…</p>;
return newCheckout ? <NewCheckout /> : <ClassicCheckout />;
}The second argument to useFlag is the default: what the app uses until flags
have loaded. useFlagsLoaded holds the checkout back until then, so nobody
watches it switch from one version to the other.
Use it from a server component
Your pages can stay server components. Render the client component inside them:
// app/page.tsx
import { Checkout } from "./checkout";
export default function CartPage() {
return (
<main>
<h1>Your cart</h1>
<Checkout />
</main>
);
}During server rendering and hydration, useFlag returns the default and
useFlagsLoaded returns false, so the server and the browser agree and
there are no hydration mismatches. The real values take over once flags load
in the browser.
Turn it on
Create a new-checkout flag in Flagbearer and turn it on. The page updates
within seconds, with no redeploy and no reload.
Tell the client who is signed in
Rollouts work per user, so tell the client who's signed in as soon as you know:
flags.identify({ key: user.id, email: user.email });The email is optional. Include it to turn flags on for your testers by email.
Flags on the server
A flag checked in the browser decides what people see. For anything about access, pricing or data, check the flag on your server too, with the Node SDK.
Next: roll it out
With the flag in place, the feature can go to your team first and then to everyone, a step at a time. See how to roll a feature out gradually.