Flagbearer

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/web

Add 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.

Start freeRead the docs