惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tenable Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
V
Vulnerabilities – Threatpost
G
GRAHAM CLULEY
Simon Willison's Weblog
Simon Willison's Weblog
C
CXSECURITY Database RSS Feed - CXSecurity.com
P
Privacy International News Feed
H
Heimdal Security Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Secure Thoughts
MyScale Blog
MyScale Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
V
Visual Studio Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
L
LINUX DO - 最新话题
D
Darknet – Hacking Tools, Hacker News & Cyber Security
The Cloudflare Blog
美团技术团队
Recorded Future
Recorded Future
T
Tailwind CSS Blog
Latest news
Latest news
Security Archives - TechRepublic
Security Archives - TechRepublic
Security Latest
Security Latest
Know Your Adversary
Know Your Adversary
Cloudbric
Cloudbric
Schneier on Security
Schneier on Security
I
Intezer
L
LINUX DO - 热门话题
P
Palo Alto Networks Blog
云风的 BLOG
云风的 BLOG
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Vercel News
Vercel News
Attack and Defense Labs
Attack and Defense Labs
人人都是产品经理
人人都是产品经理
L
LangChain Blog
爱范儿
爱范儿
博客园 - 三生石上(FineUI控件)
博客园 - 叶小钗
L
Lohrmann on Cybersecurity
S
SegmentFault 最新的问题
W
WeLiveSecurity
C
Cybersecurity and Infrastructure Security Agency CISA
S
Securelist
SecWiki News
SecWiki News
V2EX - 技术
V2EX - 技术
IT之家
IT之家
Cyberwarzone
Cyberwarzone
F
Full Disclosure
Spread Privacy
Spread Privacy
阮一峰的网络日志
阮一峰的网络日志

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Feature Flags in Next.js: The Complete Guide (App Router)
Domenico Gio · 2026-05-01 · via DEV Community

This was originally published on rollgate.io/blog/feature-flags-nextjs.

Why Feature Flags in Next.js?

Next.js is not a typical React app. With the App Router, your code runs in multiple environments — the server, the edge, and the browser — sometimes in the same request. This makes feature flags both more powerful and more complex than in a single-page application.

Here is why feature flags matter specifically for Next.js:

Server-Side Rendering creates a decision point. When a Server Component renders on the server, you need the flag value before sending HTML to the client. If you evaluate flags only on the client, users see a flash of the wrong content (or a loading spinner) before the correct variant appears.

Edge Middleware runs before rendering. Next.js middleware can redirect or rewrite requests at the edge, before any component renders. This is the ideal place for A/B testing entire pages — route users to /pricing-a or /pricing-b based on a flag, with zero layout shift.

Hybrid apps need a unified approach. A real Next.js application uses Server Components, Client Components, API routes, and middleware. You need feature flags that work consistently across all four, with the same flag key returning the same value for the same user regardless of where you evaluate it.

Feature flags in Next.js let you do gradual rollouts without redeploying, run A/B tests at the edge, and kill problematic features in seconds — all while keeping the performance benefits of server rendering.

Architecture: Where to Evaluate Flags

Before writing code, understand the four places you can evaluate feature flags in a Next.js App Router application:

Location SDK Runs On Best For
Server Components @rollgate/sdk-node Server (Node.js) Initial render, SEO content, data-dependent flags
Client Components @rollgate/sdk-react Browser Interactive UI, real-time updates, user preferences
Middleware @rollgate/sdk-node Edge/Node A/B testing pages, redirects, geo-targeting
API Routes @rollgate/sdk-node Server (Node.js) Backend logic, rate limits, feature-gated endpoints

The general rule: evaluate flags as early as possible. If a flag controls what the user sees on page load, evaluate it on the server. If a flag controls interactive behavior after the page loads, evaluate it on the client.

Server-Side Feature Flags

Server Components in Next.js are async by default, which makes them a natural fit for feature flag evaluation. You fetch the flag value during rendering, and the user receives the correct HTML immediately — no loading state, no flash of wrong content.

Setup

Install the Node.js SDK:

npm install @rollgate/sdk-node

Enter fullscreen mode Exit fullscreen mode

Create a shared Rollgate client that you can import anywhere on the server:

// lib/rollgate.ts
import { RollgateClient } from '@rollgate/sdk-node';

export const rollgate = new RollgateClient({
  apiKey: process.env.ROLLGATE_SERVER_KEY!,
  baseUrl: 'https://api.rollgate.io',
});

Enter fullscreen mode Exit fullscreen mode

Using Flags in Server Components

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

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

  const showNewDashboard = await rollgate.isEnabled('new-dashboard', {
    userId: user.id,
    attributes: {
      plan: user.plan,
      email: user.email,
      createdAt: user.createdAt,
    },
  });

  if (showNewDashboard) {
    return <NewDashboard user={user} />;
  }

  return <LegacyDashboard user={user} />;
}

Enter fullscreen mode Exit fullscreen mode

The userId ensures consistent evaluation — the same user always sees the same variant, even across multiple requests. The attributes enable targeting rules like "enable for Pro plan users" or "enable for users created after March 2026."

Server Components with Multiple Flags

When you need several flags in one component, evaluate them in parallel to avoid sequential waterfalls:

// app/settings/page.tsx
import { rollgate } from '@/lib/rollgate';
import { getCurrentUser } from '@/lib/auth';

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

  const context = {
    userId: user.id,
    attributes: { plan: user.plan },
  };

  const [showBilling, showTeamSettings, showApiKeys] = await Promise.all([
    rollgate.isEnabled('settings-billing-v2', context),
    rollgate.isEnabled('team-management', context),
    rollgate.isEnabled('api-keys-section', context),
  ]);

  return (
    <div>
      <ProfileSettings />
      {showBilling && <BillingV2 />}
      {showTeamSettings && <TeamSettings />}
      {showApiKeys && <ApiKeysSection />}
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Client-Side Feature Flags

Not everything runs on the server. Interactive components — modals, tooltips, dynamic forms, real-time features — live in Client Components. For these, use the React SDK, which provides a context provider and hooks.

Setup

Install the React SDK:

npm install @rollgate/sdk-react

Enter fullscreen mode Exit fullscreen mode

Add the RollgateProvider in your root layout. Since providers require 'use client', create a wrapper component:

// components/providers.tsx
'use client';

import { RollgateProvider } from '@rollgate/sdk-react';

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <RollgateProvider
      apiKey={process.env.NEXT_PUBLIC_ROLLGATE_CLIENT_KEY!}
      baseUrl="https://api.rollgate.io"
    >
      {children}
    </RollgateProvider>
  );
}

Enter fullscreen mode Exit fullscreen mode

// app/layout.tsx
import { Providers } from '@/components/providers';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

Enter fullscreen mode Exit fullscreen mode

Using Flags in Client Components

// components/feedback-widget.tsx
'use client';

import { useFlag } from '@rollgate/sdk-react';

export function FeedbackWidget() {
  const showFeedback = useFlag('feedback-widget');

  if (!showFeedback) return null;

  return (
    <div className="fixed bottom-4 right-4">
      <button className="rounded-full bg-blue-600 p-3 text-white shadow-lg">
        💬 Feedback
      </button>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

The useFlag hook returns the flag's current value and automatically re-renders when the value changes (for example, if you toggle the flag in the Rollgate dashboard while the user has the page open).

User Context on the Client

To enable user targeting on the client, pass user context to the provider:

// components/providers.tsx
'use client';

import { RollgateProvider } from '@rollgate/sdk-react';

interface ProvidersProps {
  children: React.ReactNode;
  user?: {
    id: string;
    plan: string;
    email: string;
  };
}

export function Providers({ children, user }: ProvidersProps) {
  return (
    <RollgateProvider
      apiKey={process.env.NEXT_PUBLIC_ROLLGATE_CLIENT_KEY!}
      baseUrl="https://api.rollgate.io"
      context={user ? {
        userId: user.id,
        attributes: { plan: user.plan, email: user.email },
      } : undefined}
    >
      {children}
    </RollgateProvider>
  );
}

Enter fullscreen mode Exit fullscreen mode

Middleware Feature Flags

Next.js middleware runs before any rendering happens. This makes it the ideal location for feature flag evaluations that control routing: A/B testing entire pages, geo-based redirects, or blocking access to unreleased sections.

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { RollgateClient } from '@rollgate/sdk-node';

const rollgate = new RollgateClient({
  apiKey: process.env.ROLLGATE_SERVER_KEY!,
  baseUrl: 'https://api.rollgate.io',
});

export async function middleware(request: NextRequest) {
  // Use a cookie for consistent assignment
  const userId = request.cookies.get('visitor-id')?.value ?? crypto.randomUUID();

  const newPricingPage = await rollgate.isEnabled('new-pricing-page', {
    userId,
  });

  // A/B test: rewrite to a different page variant
  if (request.nextUrl.pathname === '/pricing' && newPricingPage) {
    const response = NextResponse.rewrite(new URL('/pricing-b', request.url));
    // Persist the visitor ID so they always see the same variant
    response.cookies.set('visitor-id', userId, { maxAge: 60 * 60 * 24 * 30 });
    return response;
  }

  // Gate access to unreleased features
  const betaEnabled = await rollgate.isEnabled('beta-access', { userId });

  if (request.nextUrl.pathname.startsWith('/beta') && !betaEnabled) {
    return NextResponse.redirect(new URL('/waitlist', request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/pricing', '/beta/:path*'],
};

Enter fullscreen mode Exit fullscreen mode

This pattern is especially powerful for A/B testing landing pages. Users are routed to different page variants before any JavaScript runs, so there is zero layout shift and search engines see a complete page.

Combining Server and Client Flags

In many cases, you evaluate a flag on the server and pass the result to a Client Component. This avoids a second network request on the client and eliminates the loading state.

// app/page.tsx (Server Component)
import { rollgate } from '@/lib/rollgate';
import { getCurrentUser } from '@/lib/auth';
import { HeroBanner } from '@/components/hero-banner';

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

  const showNewHero = await rollgate.isEnabled('new-hero-banner', {
    userId: user?.id,
  });

  return (
    <main>
      <HeroBanner variant={showNewHero ? 'new' : 'classic'} />
      {/* ... */}
    </main>
  );
}

Enter fullscreen mode Exit fullscreen mode

// components/hero-banner.tsx (Client Component)
'use client';

import { useState } from 'react';

interface HeroBannerProps {
  variant: 'new' | 'classic';
}

export function HeroBanner({ variant }: HeroBannerProps) {
  const [email, setEmail] = useState('');

  if (variant === 'new') {
    return (
      <section className="bg-gradient-to-r from-blue-600 to-purple-600 py-20">
        <h1 className="text-5xl font-bold text-white">Ship Faster with Feature Flags</h1>
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          placeholder="Enter your email"
          className="mt-6 rounded-lg px-4 py-3"
        />
      </section>
    );
  }

  return (
    <section className="bg-gray-900 py-16">
      <h1 className="text-4xl font-bold text-white">Feature Flag Management</h1>
    </section>
  );
}

Enter fullscreen mode Exit fullscreen mode

This pattern gives you the best of both worlds: server-evaluated flags (fast, no flash) with client-side interactivity (state, event handlers).

Feature Flags in API Routes

Next.js Route Handlers are regular server-side code. Use @rollgate/sdk-node the same way you would in any Node.js backend:

// app/api/search/route.ts
import { NextResponse } from 'next/server';
import { rollgate } from '@/lib/rollgate';
import { getCurrentUser } from '@/lib/auth';

export async function GET(request: Request) {
  const user = await getCurrentUser();
  const { searchParams } = new URL(request.url);
  const query = searchParams.get('q') ?? '';

  const useNewSearchAlgorithm = await rollgate.isEnabled('search-v2', {
    userId: user?.id,
    attributes: { plan: user?.plan },
  });

  const results = useNewSearchAlgorithm
    ? await searchV2(query)
    : await searchV1(query);

  return NextResponse.json({ results });
}

Enter fullscreen mode Exit fullscreen mode

This is useful for gating backend behavior: new algorithms, different rate limits per plan, or enabling experimental API responses.

// app/api/export/route.ts
import { NextResponse } from 'next/server';
import { rollgate } from '@/lib/rollgate';
import { getCurrentUser } from '@/lib/auth';

export async function POST(request: Request) {
  const user = await getCurrentUser();

  const csvExportEnabled = await rollgate.isEnabled('csv-export', {
    userId: user?.id,
    attributes: { plan: user?.plan },
  });

  if (!csvExportEnabled) {
    return NextResponse.json(
      { error: 'CSV export is not available for your account' },
      { status: 403 }
    );
  }

  const data = await request.json();
  const csv = await generateCsv(data);

  return new NextResponse(csv, {
    headers: { 'Content-Type': 'text/csv' },
  });
}

Enter fullscreen mode Exit fullscreen mode

Performance Considerations

Feature flags add network requests. In a Next.js app with multiple rendering environments, this can become a bottleneck if you are not careful. Here is how to keep things fast.

Cache Flag Values

The Rollgate Node SDK caches flag evaluations by default. On the server, flag values are cached in memory and refreshed periodically, so subsequent isEnabled() calls for the same flag do not make additional API requests.

// lib/rollgate.ts
import { RollgateClient } from '@rollgate/sdk-node';

export const rollgate = new RollgateClient({
  apiKey: process.env.ROLLGATE_SERVER_KEY!,
  baseUrl: 'https://api.rollgate.io',
  cache: {
    ttl: 30_000, // Cache flag values for 30 seconds
  },
});

Enter fullscreen mode Exit fullscreen mode

Avoid Waterfalls

Never evaluate flags sequentially when you can evaluate them in parallel:

// Bad: sequential — each flag waits for the previous one
const flagA = await rollgate.isEnabled('flag-a', ctx);
const flagB = await rollgate.isEnabled('flag-b', ctx);
const flagC = await rollgate.isEnabled('flag-c', ctx);

// Good: parallel — all flags evaluated at once
const [flagA, flagB, flagC] = await Promise.all([
  rollgate.isEnabled('flag-a', ctx),
  rollgate.isEnabled('flag-b', ctx),
  rollgate.isEnabled('flag-c', ctx),
]);

Enter fullscreen mode Exit fullscreen mode

SSE for Real-Time Updates

The React SDK uses Server-Sent Events to receive flag changes in real time. When you toggle a flag in the Rollgate dashboard, connected clients update within seconds — without polling. This is particularly useful for kill switches, where you need to disable a feature across all active sessions immediately.

Minimize Client-Side Flag Checks

Evaluate flags on the server whenever possible. Each client-side flag check requires the browser to fetch flag values from the Rollgate API. If a flag controls static content that does not change after page load, evaluate it in a Server Component and pass the result as props.

Testing Feature Flags in Next.js

Feature flags introduce branching logic. Every flag creates two code paths, and both need testing.

Testing Server Components

Mock the Rollgate client in your tests to control flag values:

// __tests__/dashboard.test.tsx
import { render, screen } from '@testing-library/react';
import { rollgate } from '@/lib/rollgate';
import DashboardPage from '@/app/dashboard/page';

jest.mock('@/lib/rollgate', () => ({
  rollgate: {
    isEnabled: jest.fn(),
  },
}));

describe('DashboardPage', () => {
  it('renders the new dashboard when flag is enabled', async () => {
    (rollgate.isEnabled as jest.Mock).mockResolvedValue(true);

    const page = await DashboardPage();
    render(page);

    expect(screen.getByText('New Dashboard')).toBeInTheDocument();
  });

  it('renders the legacy dashboard when flag is disabled', async () => {
    (rollgate.isEnabled as jest.Mock).mockResolvedValue(false);

    const page = await DashboardPage();
    render(page);

    expect(screen.getByText('Legacy Dashboard')).toBeInTheDocument();
  });
});

Enter fullscreen mode Exit fullscreen mode

Testing Client Components

The React SDK exports a MockRollgateProvider for tests:

// __tests__/feedback-widget.test.tsx
import { render, screen } from '@testing-library/react';
import { MockRollgateProvider } from '@rollgate/sdk-react/testing';
import { FeedbackWidget } from '@/components/feedback-widget';

describe('FeedbackWidget', () => {
  it('renders when flag is enabled', () => {
    render(
      <MockRollgateProvider flags={{ 'feedback-widget': true }}>
        <FeedbackWidget />
      </MockRollgateProvider>
    );

    expect(screen.getByText('💬 Feedback')).toBeInTheDocument();
  });

  it('does not render when flag is disabled', () => {
    render(
      <MockRollgateProvider flags={{ 'feedback-widget': false }}>
        <FeedbackWidget />
      </MockRollgateProvider>
    );

    expect(screen.queryByText('💬 Feedback')).not.toBeInTheDocument();
  });
});

Enter fullscreen mode Exit fullscreen mode

Test Both Code Paths

A common mistake is only testing the "flag on" path. Always test both variants. Feature flags are temporary — when you remove a flag, you want confidence that the remaining code path works correctly.

FAQ

Can I use feature flags with Next.js Static Site Generation (SSG)?

Yes, but with a caveat. Statically generated pages are built at build time, so the flag value is baked into the HTML. If you change the flag after the build, the static page will not reflect the change until the next build (or ISR revalidation). For dynamic flags, use Server Components with dynamic rendering or evaluate flags on the client.

Do feature flags affect SEO?

When evaluated on the server, no. The search engine crawler sees the fully rendered HTML, just like any other page. When evaluated on the client, flags that control visible content can cause layout shift, which may impact Core Web Vitals. Prefer server-side evaluation for SEO-critical content.

How do I handle feature flags during build time?

Use environment variables to distinguish between build and runtime. For static pages, you can evaluate flags at build time using generateStaticParams or getStaticProps (Pages Router). For App Router, prefer runtime evaluation in Server Components with export const dynamic = 'force-dynamic'.

What happens if the Rollgate API is down?

The Rollgate SDKs include built-in resilience: circuit breakers, retry logic, and local caching. If the API is unreachable, the SDK serves the last known flag values from its cache. You can also configure default values that apply when no cached data is available. See the SDK documentation for configuration options.

How many feature flags should I use in one page?

There is no hard limit, but keep it practical. If you have more than five flags on a single page, consider whether some of them can be consolidated. Each flag is a maintenance cost — when you are done with a rollout, clean up the flag to reduce complexity.

Should I use server or client flags?

Use this decision tree:

  • Does the flag control content visible on initial page load? Server.
  • Does the flag control interactive behavior after load? Client.
  • Does the flag control routing or access? Middleware.
  • Does the flag control backend logic? API route.

Get Started

Implementing feature flags in Next.js does not require rethinking your architecture. The patterns above map directly to how Next.js App Router already works: async Server Components fetch data on the server, Client Components handle interactivity, middleware handles routing, and API routes handle backend logic. Feature flags slot into each of these naturally.

Rollgate gives you a single dashboard to manage flags across all these environments, with SDKs built specifically for each pattern.

Ready to add feature flags to your Next.js app?