Get started
Back to blog

Feature flags in Next.js App Router

Next.js 14+ changes how you think about feature flags. In the old Pages Router, flags were evaluated in getServerSideProps or in the client. In App Router, you have server components by default, client components when you need them, and an edge runtime that changes the trade-offs again.

Here’s how to do it without shooting yourself in the foot.

Install

npm install @flagify/node @flagify/react

You need both: @flagify/node for server components and server actions, @flagify/react for client components.

Server components (the default)

Server components run on the server and can call @flagify/node directly. Create a singleton client and import it wherever you need flags.

// lib/flagify.ts
import { Flagify } from '@flagify/node'

export const flagify = new Flagify({
  projectKey: 'my-project',
  publicKey: process.env.FLAGIFY_PUBLIC_KEY!,
  options: { realtime: true },
})
// app/checkout/page.tsx
import { flagify } from '@/lib/flagify'
import NewCheckout from './NewCheckout'
import LegacyCheckout from './LegacyCheckout'

export default async function CheckoutPage() {
  await flagify.ready()
  const showNew = flagify.isEnabled('new-checkout-flow')

  return showNew ? <NewCheckout /> : <LegacyCheckout />
}

One gotcha: flagify.ready() blocks the render until the SDK has fetched flags. On cold starts this is a few hundred milliseconds. If you want to avoid that, evaluate with a fallback value immediately.

Per-user targeting in server components

If you have targeting rules based on user attributes, you need to evaluate per-request:

// app/dashboard/page.tsx
import { flagify } from '@/lib/flagify'
import { getCurrentUser } from '@/lib/auth'

export default async function DashboardPage() {
  const user = await getCurrentUser()

  const result = await flagify.evaluate('admin-panel', {
    id: user.id,
    role: user.role,
    plan: user.plan,
  })

  return result.value ? <AdminPanel /> : <UserPanel />
}

Don’t pass options.user to the constructor in a multi-user server. That only works for single-user processes like CLIs or scheduled jobs. For per-request evaluation, use flagify.evaluate(key, user).

Client components

Client components need @flagify/react. Wrap your app (or a subtree) in FlagifyProvider:

// app/providers.tsx
'use client'

import { FlagifyProvider } from '@flagify/react'

export function Providers({
  children,
  user,
}: {
  children: React.ReactNode
  user: { id: string; role: string } | null
}) {
  return (
    <FlagifyProvider
      key={user?.id ?? 'anonymous'}
      projectKey="my-project"
      publicKey={process.env.NEXT_PUBLIC_FLAGIFY_KEY!}
      options={{
        realtime: true,
        user: user ? { id: user.id, role: user.role } : undefined,
      }}
    >
      {children}
    </FlagifyProvider>
  )
}

Note the key={user?.id ?? 'anonymous'}. This remounts the provider when the user changes (login, logout, switch account). Without it, the client cache shows stale values for the wrong user.

Then in any client component:

'use client'
import { useFlag } from '@flagify/react'

export function NewNavbar() {
  const showNew = useFlag('new-navbar')
  return showNew ? <NavbarV2 /> : <NavbarV1 />
}

Edge runtime

If you are using the edge runtime (middleware, edge routes), @flagify/node works but with caveats. The SDK caches locally and syncs via SSE. On edge, cold starts can mean no cache. Use await flagify.ready() before any evaluation, or fetch flag values via the API directly.

// middleware.ts
import { NextResponse } from 'next/server'
import { flagify } from '@/lib/flagify'

export async function middleware(request: Request) {
  await flagify.ready()
  const maintenanceMode = flagify.isEnabled('maintenance-mode')

  if (maintenanceMode) {
    return NextResponse.rewrite(new URL('/maintenance', request.url))
  }
}

Common mistakes

Evaluating flags at module load time. Do not evaluate flags in top-level code outside a request. The SDK is not ready yet and you will get fallback values forever (because module code runs once).

Passing user in the Provider without key. You will see values for the wrong user after login/logout. Always use key={user?.id ?? 'anonymous'}.

Calling flagify.evaluate(key, user) in a React hook. Use options.user in the Provider and regular hooks like useFlag. The Provider already does per-user evaluation.

Exposing the secret key in client code. @flagify/react uses your public key (pk_*). Never put a secret key (sk_*) in anything that ships to the browser.

Deploying to Vercel

Nothing special. Set FLAGIFY_PUBLIC_KEY in your environment variables. If you use a separate project per environment (preview, production), use a different publicKey in each.


Flagify has first-class support for Next.js via our Node.js SDK and React SDK. Set up flags in your Next.js app in under five minutes with the quick start guide.

Start for free — no credit card required.