← Back to Blog
Implementation

Feature Flags in Next.js: Complete Implementation Guide

Add feature flags to your Next.js app in 5 minutes. Works with App Router, Server Components, and Edge Runtime. Full tutorial with real code examples.

RE

RemoteEnv Team

Engineering insights and best practices

March 19, 2026

12 min read

Next.js
React
feature-flags
tutorial
App Router
📚

Implementation

Feature flags are the safest way to ship new features in production without risking your entire application. If you're building with Next.js, you have a unique advantage: Server Components and Edge Runtime let you evaluate flags server-side with near-zero latency, something most React frameworks can't do natively.

This guide walks you through a complete feature flag implementation in Next.js using RemoteEnv's REST API. No SDK dependencies, no client-side latency penalties, and full compatibility with App Router, Server Components, and Edge Runtime.

Key Takeaways: - Feature flags in Next.js work with App Router, Server Components, and Edge Runtime using a simple REST API call. - No SDK dependency needed — fetch your flags server-side in layout.tsx or page.tsx and pass them to components as props. - Client-side flag evaluation adds 50-200ms latency. Server-side evaluation in Next.js middleware adds under 10ms. - RemoteEnv's API returns all flags in one request — cache it with Next.js revalidation for zero-latency subsequent reads.

Why Feature Flags Matter in Next.js

Next.js applications often serve both static and dynamic content. Feature flags let you control which features are visible without redeploying your application. This is critical for:

  • Gradual rollouts — Release a new checkout flow to 10% of users before going to 100%.
  • Kill switches — Instantly disable a feature that's causing errors in production.
  • A/B testing — Show different UI variants to different user segments.
  • Environment-specific config — Enable debug tools in staging but not production.

Without feature flags, every change requires a full deployment cycle. With them, you toggle features in seconds from a dashboard.

Server-Side vs Client-Side Flag Evaluation

Next.js gives you a choice that most React frameworks don't: evaluate flags on the server before any HTML reaches the browser.

Client-Side Evaluation (Not Recommended)

With client-side evaluation, the browser fetches flag values after the page loads. This creates several problems:

  • 50-200ms latency added to every page load while flags are fetched.
  • Flash of wrong content — users see the default state before flags load.
  • Exposed flag logic — your flag keys and targeting rules are visible in the browser's network tab.
  • Extra API calls — every client makes its own request, multiplying your API usage.

Server-Side Evaluation (Recommended)

With server-side evaluation in Next.js, flags are resolved before the page renders:

  • Under 10ms latency when evaluated in middleware or Server Components.
  • No content flash — the correct variant is rendered on the first paint.
  • Hidden flag logic — flag keys and values never reach the browser.
  • Fewer API calls — one server-side request serves many users via caching.

For Next.js applications, server-side evaluation is almost always the right choice.

Setting Up RemoteEnv

Before writing code, set up your feature flag backend. RemoteEnv takes under 5 minutes:

  1. Sign up at [app.remoteenv.com](https://app.remoteenv.com) — Free tier available, no credit card required.
  2. Create a project — Name it after your Next.js app (e.g., "my-nextjs-app").
  3. Create a feature flag — Add a boolean flag called `new-dashboard` and set it to `true` in your development environment.

Copy your project UUID and environment UUID from the RemoteEnv dashboard. You'll need these for API calls.

Add them to your .env.local file:

REMOTEENV_API_URL=https://api.remoteenv.com
REMOTEENV_PROJECT_UUID=your-project-uuid
REMOTEENV_ENV_UUID=your-environment-uuid

Fetching Feature Flags in Server Components

Next.js Server Components run only on the server, making them ideal for flag evaluation. Here's how to fetch all your feature flags in a Server Component:

// app/page.tsx
export default async function HomePage() {
  const res = await fetch(
    `${process.env.REMOTEENV_API_URL}/config/${process.env.REMOTEENV_PROJECT_UUID}?environmentUuid=${process.env.REMOTEENV_ENV_UUID}`,
    { next: { revalidate: 60 } }
  );
  const { data } = await res.json();

return ( <main> <h1>Welcome to My App</h1> {flags['new-dashboard'] && ( <NewDashboard /> )} {!flags['new-dashboard'] && ( <LegacyDashboard /> )} </main> ); }

This fetches flags once on the server, caches the result for 60 seconds, and renders the correct component. No client-side JavaScript needed for flag evaluation.

For shared flag access across multiple pages, fetch flags in your root layout:

// app/layout.tsx

export default async function RootLayout({ children }: { children: React.ReactNode }) { const res = await fetch( ${process.env.REMOTEENV_API_URL}/config/${process.env.REMOTEENV_PROJECT_UUID}?environmentUuid=${process.env.REMOTEENV_ENV_UUID}, { next: { revalidate: 60 } } ); const { data } = await res.json();

return ( <html lang="en"> <body> <FeatureFlagProvider flags={data.flags}> {children} </FeatureFlagProvider> </body> </html> ); }

Using Flags in Client Components

Client Components can't fetch data directly on the server, but they can receive flag values as props from Server Components. Create a React Context to make flags available throughout your component tree:

// app/providers/feature-flags.tsx

import { createContext, useContext, ReactNode } from 'react';

type Flags = Record<string, boolean | string | number>;

const FeatureFlagContext = createContext<Flags>({});

export function FeatureFlagProvider({ flags, children }: { flags: Flags; children: ReactNode }) { return ( <FeatureFlagContext.Provider value={flags}> {children} </FeatureFlagContext.Provider> ); }

export function useFeatureFlags() { return useContext(FeatureFlagContext); }

export function useFlag(key: string) { const flags = useContext(FeatureFlagContext); return flags[key]; }

Then use the hook in any Client Component:

// components/Header.tsx

import { useFlag } from '@/app/providers/feature-flags';

export function Header() { const showNewNav = useFlag('new-navigation');

return ( <header> {showNewNav ? <NewNavigation /> : <LegacyNavigation />} </header> ); }

The flags are fetched once on the server, passed to the provider, and available to every Client Component without additional API calls.

Feature Flags in Next.js Middleware

Middleware runs at the edge before any page renders. This is the fastest place to evaluate flags — under 10ms latency. Use middleware for flags that affect routing, redirects, or A/B test assignments:

// middleware.ts

export async function middleware(request: NextRequest) { const res = await fetch( ${process.env.REMOTEENV_API_URL}/config/${process.env.REMOTEENV_PROJECT_UUID}?environmentUuid=${process.env.REMOTEENV_ENV_UUID} ); const { data } = await res.json(); const flags = data.flags;

// Redirect to maintenance page if flag is enabled if (flags['maintenance-mode'] && request.nextUrl.pathname !== '/maintenance') { return NextResponse.redirect(new URL('/maintenance', request.url)); }

// Rewrite to A/B test variant if (flags['new-pricing-page'] && request.nextUrl.pathname === '/pricing') { return NextResponse.rewrite(new URL('/pricing-v2', request.url)); }

return NextResponse.next(); }

export const config = { matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], };

Middleware-level flags are evaluated before the page even starts rendering. This is ideal for maintenance mode toggles, geo-based routing, and A/B test URL rewrites.

Caching Strategy

Next.js has built-in caching that pairs perfectly with feature flags. The key is choosing the right revalidation interval:

Static Revalidation (Recommended for most flags)

const res = await fetch(
  `${process.env.REMOTEENV_API_URL}/config/${process.env.REMOTEENV_PROJECT_UUID}?environmentUuid=${process.env.REMOTEENV_ENV_UUID}`,
  { next: { revalidate: 60 } }
);

This caches flag values for 60 seconds. After a flag change in RemoteEnv, the new value propagates within one minute. For most feature flags, this is the right balance of freshness and performance.

On-Demand Revalidation (For instant flag updates)

If you need instant flag propagation, use Next.js on-demand revalidation with a webhook:

// app/api/revalidate-flags/route.ts
import { revalidateTag } from 'next/cache';

export async function POST(request: NextRequest) { const secret = request.headers.get('x-revalidation-secret'); if (secret !== process.env.REVALIDATION_SECRET) { return new Response('Unauthorized', { status: 401 }); }

revalidateTag('feature-flags'); return new Response('Revalidated', { status: 200 }); }

Then tag your flag fetch:

const res = await fetch(
  `${process.env.REMOTEENV_API_URL}/config/${process.env.REMOTEENV_PROJECT_UUID}?environmentUuid=${process.env.REMOTEENV_ENV_UUID}`,
  { next: { tags: ['feature-flags'] } }
);

Now when you change a flag in RemoteEnv, hit the revalidation endpoint and all pages instantly reflect the new value.

No Cache (For real-time flags)

const res = await fetch(
  `${process.env.REMOTEENV_API_URL}/config/${process.env.REMOTEENV_PROJECT_UUID}?environmentUuid=${process.env.REMOTEENV_ENV_UUID}`,
  { cache: 'no-store' }
);

Use this only when you need every request to have the absolute latest flag values. This adds a network call to every page render.

Gradual Rollouts with Next.js

Gradual rollouts let you release a feature to a percentage of users. In Next.js, you can implement this using cookies to ensure users see a consistent experience:

// middleware.ts

export async function middleware(request: NextRequest) { const res = await fetch( ${process.env.REMOTEENV_API_URL}/config/${process.env.REMOTEENV_PROJECT_UUID}?environmentUuid=${process.env.REMOTEENV_ENV_UUID} ); const { data } = await res.json(); const rolloutPercentage = data.flags['new-checkout-rollout'] || 0;

// Get or create a consistent user bucket let userBucket = request.cookies.get('feature-bucket')?.value; if (!userBucket) { userBucket = String(Math.floor(Math.random() * 100)); }

const response = NextResponse.next(); response.cookies.set('feature-bucket', userBucket, { maxAge: 60 * 60 * 24 * 30 });

// Set a header that Server Components can read const isInRollout = parseInt(userBucket) < rolloutPercentage; response.headers.set('x-new-checkout', isInRollout ? 'true' : 'false');

return response; }

This assigns each user a random bucket (0-99) stored in a cookie. If the bucket number is below the rollout percentage, the user gets the new feature. Increase the percentage in RemoteEnv to gradually roll out to more users.

Testing Feature Flags Locally

During development, you want full control over flag values without changing them in RemoteEnv. Here are three approaches:

Use a Development Environment

Create a separate environment in RemoteEnv for local development. Set flags to whatever values you need without affecting staging or production.

Environment Variable Overrides

Create a utility that lets you override flags via environment variables:

// lib/flags.ts
export async function getFlags() {
  const res = await fetch(
    `${process.env.REMOTEENV_API_URL}/config/${process.env.REMOTEENV_PROJECT_UUID}?environmentUuid=${process.env.REMOTEENV_ENV_UUID}`,
    { next: { revalidate: 60 } }
  );
  const { data } = await res.json();

// Allow local overrides via env vars if (process.env.NODE_ENV === 'development') { const overrides = process.env.FLAG_OVERRIDES; if (overrides) { const parsed = JSON.parse(overrides); return { ...flags, ...parsed }; } }

return flags; }

Then in .env.local:

FLAG_OVERRIDES={"new-dashboard": true, "new-checkout-rollout": 100}

Mock Flags in Tests

For unit and integration tests, mock the flag provider:

// __tests__/components/Header.test.tsx
import { render } from '@testing-library/react';
import { FeatureFlagProvider } from '@/app/providers/feature-flags';

test('shows new navigation when flag is enabled', () => { const { getByText } = render( <FeatureFlagProvider flags={{ 'new-navigation': true }}> <Header /> </FeatureFlagProvider> ); expect(getByText('New Nav')).toBeInTheDocument(); });

Putting It All Together

Here's the recommended architecture for feature flags in a Next.js application:

  1. Fetch flags in `layout.tsx` using RemoteEnv's REST API with 60-second revalidation.
  2. Pass flags via React Context to make them available in Client Components.
  3. Use middleware for flags that affect routing, redirects, or A/B test assignments.
  4. Cache aggressively — most flags don't change every second.
  5. Use a development environment in RemoteEnv for local testing.

This pattern gives you fast flag evaluation, no SDK dependencies, and full compatibility with Next.js App Router features.

Related Resources

  • [Instant Rollback Tools Compared](/rollback-comparison) — See how rollback speed varies across feature flag platforms
  • [Feature Flag Rollback Guide](/feature-flag-rollback) — Best practices for using flags as kill switches
  • [Feature Toggle Tools Comparison 2025](/blog/feature-toggle-tools-comparison-2025) — Compare pricing and features across all major platforms

Ready to implement feature flags?

Start your free trial and see how RemoteEnv can transform your deployment process.

Try RemoteEnv Free

Related Articles