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

推荐订阅源

V
Vulnerabilities – Threatpost
P
Proofpoint News Feed
The Hacker News
The Hacker News
Know Your Adversary
Know Your Adversary
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tenable Blog
AWS News Blog
AWS News Blog
S
Securelist
T
Threatpost
C
Cybersecurity and Infrastructure Security Agency CISA
IT之家
IT之家
腾讯CDC
WordPress大学
WordPress大学
Spread Privacy
Spread Privacy
C
Check Point Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Engineering at Meta
Engineering at Meta
Latest news
Latest news
A
About on SuperTechFans
The Register - Security
The Register - Security
L
LINUX DO - 热门话题
T
The Exploit Database - CXSecurity.com
C
Cisco Blogs
T
Tailwind CSS Blog
Simon Willison's Weblog
Simon Willison's Weblog
阮一峰的网络日志
阮一峰的网络日志
MyScale Blog
MyScale Blog
大猫的无限游戏
大猫的无限游戏
T
Tor Project blog
L
Lohrmann on Cybersecurity
G
GRAHAM CLULEY
B
Blog RSS Feed
Scott Helme
Scott Helme
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
NISL@THU
NISL@THU
P
Privacy International News Feed
Security Latest
Security Latest
Recorded Future
Recorded Future
L
LangChain Blog
Cyberwarzone
Cyberwarzone
C
Cyber Attacks, Cyber Crime and Cyber Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
博客园 - 聂微东
Google DeepMind News
Google DeepMind News
Last Week in AI
Last Week in AI
Apple Machine Learning Research
Apple Machine Learning Research
F
Fortinet All Blogs
O
OpenAI News
T
Threat Research - Cisco Blogs
Blog — PlanetScale
Blog — PlanetScale

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 em React/Next.js: Guia Completo com ConfigCat e Context API
Guilherme Ma · 2026-05-19 · via DEV Community

Introdução

Se você já passou por alguma dessas situações, este artigo é pra você:

  • Precisou fazer rollback de um deploy e isso bloqueou toda a equipe de subir código
  • Teve conflitos intermináveis na branch develop porque várias features estavam sendo desenvolvidas ao mesmo tempo
  • Quis testar uma funcionalidade em produção apenas com um grupo específico de usuários antes de liberar pra todos
  • Descobriu um bug crítico em produção e precisou esperar um novo deploy pra resolver

Feature Flags (ou Feature Toggles) resolvem todos esses problemas. Neste guia, vou mostrar passo a passo como implementei feature flags em uma aplicação Next.js usando ConfigCat como serviço externo e React Context API como camada de abstração — uma arquitetura desacoplada, escalável e pronta pra produção.


O que são Feature Flags?

Feature Flags são interruptores que permitem ligar e desligar funcionalidades da sua aplicação sem precisar fazer deploy. O código da feature já está em produção, mas só é executado quando a flag está ativa.

Pense assim: é como ter um painel de controle onde você decide, em tempo real, o que cada usuário vê na aplicação.

O que você ganha com isso?

  • Deploy contínuo sem medo — suba o código com a feature desligada
  • Teste em produção com segurança — ligue apenas pro seu usuário
  • Kill switch instantâneo — desligou a flag, a feature some em segundos
  • Rollout gradual — libere pra 10%, depois 50%, depois 100%
  • Sem conflitos de branch — todo mundo sobe pra main/develop, a flag controla quem vê o quê

Por que ConfigCat?

Existem várias ferramentas de feature flags no mercado (LaunchDarkly, Unleash, Split.io). Escolhi o ConfigCat por:

  1. Interface intuitiva — dashboard simples e direto ao ponto
  2. Suporte nativo a React — SDK com Provider e hooks prontos
  3. Targeting avançado — controle por usuário, atributos customizados, porcentagem
  4. Plano gratuito generoso — suficiente pra começar
  5. Polling automático — atualiza os valores das flags periodicamente sem você precisar fazer nada

Arquitetura da Solução

A decisão mais importante da implementação: não acoplar a aplicação ao ConfigCat.

Se amanhã você precisar trocar de ferramenta, só altera o Provider. Nenhum componente da aplicação sabe que o ConfigCat existe.

Estrutura de pastas

src/
├── utils/consts/
│   └── featureFlags.ts          # Constantes das flags + SDK keys
├── providers/feature-flags/
│   └── featureFlagsContext.tsx   # Context + Provider + sincronização
└── pages/
    └── _app.tsx                 # Provider na árvore de componentes

Enter fullscreen mode Exit fullscreen mode

Fluxo da arquitetura

ConfigCat (serviço externo)
    ↓ polling a cada N segundos
ConfigCatReactProvider (engine — busca, cache, polling)
    ↓
ConfigCatSync (sincroniza user + busca flags boolean e string + escuta mudanças)
    ↓ armazena no useState
FeatureFlagsContext.Provider (Context próprio do projeto)
    ↓ expõe via useContext
Componentes (consomem com getFlag ou getStringFlag, não sabem que ConfigCat existe)

Enter fullscreen mode Exit fullscreen mode


Implementação Passo a Passo

1. Instalação

npm install configcat-react

Enter fullscreen mode Exit fullscreen mode

2. Constantes tipadas

Primeiro, crie as constantes das flags e as SDK keys. A implementação suporta dois tipos de flags: boolean (liga/desliga) e string (valor variável, útil para A/B testing e conteúdo dinâmico).

// src/utils/consts/featureFlags.ts

export const featureFlags = {
  SHOW_DASHBOARD: 'showDashboard',
  ENABLE_NEW_CHECKOUT: 'enableNewCheckout'
} as const

export const stringFeatureFlags = {
  ACTIVE_PROMOTION: 'activePromotion'
} as const

export type FeatureFlagKey = (typeof featureFlags)[keyof typeof featureFlags]
export type StringFeatureFlagKey = (typeof stringFeatureFlags)[keyof typeof stringFeatureFlags]

export const configCatSdkKeys = {
  TEST: '<your-configcat-sdk-key-test>',
  PRODUCTION: '<your-configcat-sdk-key-production>'
} as const

Enter fullscreen mode Exit fullscreen mode

Por que tipar? O TypeScript garante que você não vai digitar o nome errado de uma flag. Se tentar usar uma string que não existe no objeto, o compilador avisa.

Por que dois objetos separados? Flags boolean e string têm contratos de retorno diferentes — { isEnabled, isLoading } vs { value, isLoading }. Separar os objetos permite tipar cada método (getFlag e getStringFlag) de forma precisa, sem abrir mão de type safety.

Por que as SDK keys estão no código e não em .env? As SDK keys do ConfigCat são públicas por design — elas só permitem leitura das flags, não alteração. Colocá-las no código junto com uma função de detecção de ambiente (como veremos a seguir) elimina a necessidade de gerenciar arquivos .env por ambiente.

Agora exporte as constantes no barrel do projeto pra que fiquem acessíveis:

// src/utils/consts/index.ts
import { featureFlags, stringFeatureFlags, configCatSdkKeys } from './featureFlags'

const Consts = {
  // ... outras constantes do projeto
  featureFlags,
  stringFeatureFlags,
  configCatSdkKeys
}

export default Consts

Enter fullscreen mode Exit fullscreen mode

E exporte os tipos:

// src/utils/index.ts
export type { FeatureFlagKey, StringFeatureFlagKey } from './consts/featureFlags'

Enter fullscreen mode Exit fullscreen mode

3. Detecção de ambiente

Em vez de depender de variáveis de ambiente (.env.local, .env.production), use uma função que detecta o ambiente em runtime:

// src/lib/getIsProduction.ts

const hostsProduction = [
  'app.meudominio.com.br',
  'app-stg.meudominio.com.br'
]

const getIsProduction = () =>
  process.env.BUILD_ENV === 'production' ||
  (typeof window !== 'undefined' &&
    hostsProduction.includes(window.location.host))

export default getIsProduction

Enter fullscreen mode Exit fullscreen mode

Isso permite que o Provider escolha a SDK key correta automaticamente, sem depender de arquivos .env.

4. O Provider — coração da implementação

Aqui é onde tudo acontece. O Provider tem 3 responsabilidades:

  1. Inicializar o ConfigCat com a SDK key correta
  2. Sincronizar os dados do usuário logado com o ConfigCat (pra targeting)
  3. Buscar todas as flags e expor via Context próprio do projeto
// src/providers/feature-flags/featureFlagsContext.tsx
'use client'

import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState
} from 'react'

import {
  ConfigCatProvider as ConfigCatReactProvider,
  useConfigCatClient,
  User
} from 'configcat-react'

import { getIsProduction } from 'lib'

import { Consts, FeatureFlagKey, StringFeatureFlagKey } from 'utils'

import { UserContext } from 'providers/user/userContext'

type FlagState = {
  value: boolean | string
  loading: boolean
}

type FeatureFlagsContextProps = {
  flags: Record<string, FlagState>
  getFlag: (key: FeatureFlagKey) => { isEnabled: boolean; isLoading: boolean }
  getStringFlag: (key: StringFeatureFlagKey) => { value: string; isLoading: boolean }
}

const defaultFlagState = { isEnabled: false, isLoading: true }
const defaultStringFlagState = { value: '', isLoading: true }

export const FeatureFlagsContext = createContext<FeatureFlagsContextProps>({
  flags: {},
  getFlag: () => defaultFlagState,
  getStringFlag: () => defaultStringFlagState
})

Enter fullscreen mode Exit fullscreen mode

Detalhe importante: o defaultFlagState retorna isEnabled: false e isLoading: true. O defaultStringFlagState retorna value: '' e isLoading: true. Enquanto as flags não carregaram, nenhuma feature controlada por flag aparece — comportamento seguro por padrão.

ConfigCatSync — o componente que faz a mágica

const ConfigCatSync = ({ children }: { children: React.ReactNode }) => {
  const { user } = useContext(UserContext)
  const client = useConfigCatClient()
  const [flags, setFlags] = useState<Record<string, FlagState>>({})

  // Sincroniza dados do usuário logado com o ConfigCat
  useEffect(() => {
    if (!client) return

    if (user) {
      const configCatUser = new User(user.email, user.email, undefined, {
        role: user.role || '',
        plan: user.plan || '',
        is_admin: user.isAdmin ? '1' : '0'
        // Adicione quantos atributos precisar pro targeting
      })

      client.setDefaultUser(configCatUser)
    } else {
      client.clearDefaultUser()
    }
  }, [client, user])

  // Busca todas as flags e escuta mudanças
  useEffect(() => {
    if (!client) return

    const fetchAllFlags = async () => {
      const booleanKeys = Object.values(Consts.featureFlags)
      const stringKeys = Object.values(Consts.stringFeatureFlags)

      const booleanEntries = await Promise.all(
        booleanKeys.map(async (key) => {
          const value = await client.getValueAsync(key, false)
          return [key, { value: Boolean(value), loading: false }] as const
        })
      )

      const stringEntries = await Promise.all(
        stringKeys.map(async (key) => {
          const value = await client.getValueAsync(key, '')
          return [key, { value: String(value), loading: false }] as const
        })
      )

      setFlags(Object.fromEntries([...booleanEntries, ...stringEntries]))
    }

    fetchAllFlags()

    const handler = () => {
      fetchAllFlags()
    }

    client.on('configChanged', handler)

    return () => {
      client.off('configChanged', handler)
    }
  }, [client, user])

  const getFlag = useCallback(
    (key: FeatureFlagKey) => {
      const flag = flags[key]
      if (!flag) return defaultFlagState
      return { isEnabled: Boolean(flag.value), isLoading: flag.loading }
    },
    [flags]
  )

  const getStringFlag = useCallback(
    (key: StringFeatureFlagKey) => {
      const flag = flags[key]
      if (!flag) return defaultStringFlagState
      return { value: String(flag.value), isLoading: flag.loading }
    },
    [flags]
  )

  const contextValue = useMemo(
    () => ({ flags, getFlag, getStringFlag }),
    [flags, getFlag, getStringFlag]
  )

  return (
    <FeatureFlagsContext.Provider value={contextValue}>
      {children}
    </FeatureFlagsContext.Provider>
  )
}

Enter fullscreen mode Exit fullscreen mode

O que está acontecendo aqui:

  • O primeiro useEffect sincroniza os dados do usuário logado com o ConfigCat. Isso permite targeting por atributos (ex: "mostrar só pra admins")
  • O segundo useEffect busca todas as flags de uma vez — boolean e string separadamente — e armazena no useState. Note que user faz parte das dependências: quando o usuário troca (ex: login/logout), as flags são rebuscadas automaticamente com o novo contexto de targeting
  • O client.on('configChanged', handler) escuta mudanças: quando o ConfigCat detecta uma atualização via polling, o handler rebusca todas as flags automaticamente, sem refresh da página
  • O useMemo no contextValue evita re-renders desnecessários nos componentes consumidores

O Provider principal

type FeatureFlagsProviderProps = {
  children: React.ReactNode
}

const FeatureFlagsProvider = ({ children }: FeatureFlagsProviderProps) => {
  const sdkKey = getIsProduction()
    ? Consts.configCatSdkKeys.PRODUCTION
    : Consts.configCatSdkKeys.TEST

  return (
    <ConfigCatReactProvider
      sdkKey={sdkKey}
      options={{ pollIntervalSeconds: 3600 }}
    >
      <ConfigCatSync>{children}</ConfigCatSync>
    </ConfigCatReactProvider>
  )
}

export default FeatureFlagsProvider

Enter fullscreen mode Exit fullscreen mode

Sobre o pollIntervalSeconds: o padrão do ConfigCat é 60 segundos. Usamos 3600 (1 hora) porque, na maioria dos casos, flags não precisam propagar imediatamente — e intervalos curtos consomem quota do plano desnecessariamente. Se o seu cenário exigir propagação mais rápida (ex: kill switch urgente), reduzir para 60 ou 120 é razoável. O importante é calibrar conforme a necessidade real, não usar o menor valor possível.

5. Adicionar na árvore de componentes

// src/pages/_app.tsx
import FeatureFlagsProvider from 'providers/feature-flags/featureFlagsContext'

function App({ Component, pageProps }) {
  return (
    <UserProvider>
      <FeatureFlagsProvider>
        <Component {...pageProps} />
      </FeatureFlagsProvider>
    </UserProvider>
  )
}

Enter fullscreen mode Exit fullscreen mode

Ordem importa: o FeatureFlagsProvider precisa estar dentro do UserProvider, porque o ConfigCatSync consome o UserContext pra sincronizar os dados do usuário.


Configurando no Dashboard do ConfigCat

Criando uma flag boolean

  1. Acesse o dashboard do ConfigCat
  2. Crie uma feature flag do tipo On/Off Toggle com o nome exato que você definiu na constante (ex: showDashboard)
  3. Configure o valor padrão como OFF

Imagem do dashboard do ConfigCat mostrando a criação de uma flag boolean

Criando uma flag string

  1. No dashboard, crie uma feature flag do tipo Text
  2. Use o nome exato da constante em stringFeatureFlags (ex: activePromotion)
  3. Configure o valor padrão como string vazia ou um valor seguro

Imagem do dashboard do ConfigCat mostrando a criação de uma flag string

Configurando ambientes

O ConfigCat separa ambientes por SDK key. Cada ambiente tem sua própria key:

  • Test/Development — use a key de teste
  • Production — use a key de produção

Isso significa que você pode ter a flag ON em teste e OFF em produção ao mesmo tempo.

Imagem dos ambientes Test e Production


Consumindo nos Componentes

Exemplo 1: Controlar visibilidade de um item no menu

import { useContext, useMemo } from 'react'

import { Consts } from 'utils'

import { FeatureFlagsContext } from 'providers/feature-flags/featureFlagsContext'

const Menu = ({ loggedUser }) => {
  const { getFlag } = useContext(FeatureFlagsContext)
  const { isEnabled: showDashboard } = getFlag(
    Consts.featureFlags.SHOW_DASHBOARD
  )

  const menuItems = useMemo(() => {
    const base = [
      { label: 'Parcelas', url: '/app/parcelas' },
      { label: 'Recibos', url: '/app/recibos' }
    ]

    if (loggedUser.isAdmin && showDashboard) {
      base.unshift({ label: 'Dashboard', url: '/app/dashboard' })
    }

    return base
  }, [loggedUser, showDashboard])

  return (
    <nav>
      <ul>
        {menuItems.map((item) => (
          <li key={item.url}>
            <a href={item.url}>{item.label}</a>
          </li>
        ))}
      </ul>
    </nav>
  )
}

Enter fullscreen mode Exit fullscreen mode

Perceba: o componente importa FeatureFlagsContext — não sabe nada sobre ConfigCat. Se amanhã trocar de ferramenta, este componente não muda.

Exemplo 2: Proteger uma página inteira com redirect

import { useContext, useEffect } from 'react'

import { useRouter } from 'next/router'

import { Consts } from 'utils'

import { FeatureFlagsContext } from 'providers/feature-flags/featureFlagsContext'

const DashboardPage = () => {
  const { push } = useRouter()
  const { getFlag } = useContext(FeatureFlagsContext)
  const { isEnabled: showDashboard, isLoading } = getFlag(
    Consts.featureFlags.SHOW_DASHBOARD
  )

  useEffect(() => {
    if (!isLoading && !showDashboard) {
      push('/app')
    }
  }, [isLoading, showDashboard, push])

  if (isLoading) return null

  return <div>Conteúdo do Dashboard</div>
}

Enter fullscreen mode Exit fullscreen mode

Por que checar isLoading? Sem essa checagem, o componente faria redirect imediatamente no primeiro render (porque isEnabled começa como false enquanto as flags carregam). O isLoading garante que o redirect só acontece depois que o ConfigCat respondeu.

Exemplo 3: Exibição condicional de um componente

import { useContext } from 'react'

import { Consts } from 'utils'

import { FeatureFlagsContext } from 'providers/feature-flags/featureFlagsContext'

const HomePage = () => {
  const { getFlag } = useContext(FeatureFlagsContext)
  const { isEnabled: enableNewCheckout } = getFlag(
    Consts.featureFlags.ENABLE_NEW_CHECKOUT
  )

  return (
    <div>
      <h1>Bem-vindo</h1>
      {enableNewCheckout ? <NewCheckout /> : <LegacyCheckout />}
    </div>
  )
}

Enter fullscreen mode Exit fullscreen mode

Exemplo 4: Flag string para conteúdo dinâmico

Flags string são ideais quando você precisa não só de ligar/desligar, mas de controlar qual variante exibir — A/B testing, banners sazonais, temas, e afins.

import { useContext } from 'react'

import { Consts } from 'utils'

import { FeatureFlagsContext } from 'providers/feature-flags/featureFlagsContext'

const PromoBanner = () => {
  const { getStringFlag } = useContext(FeatureFlagsContext)
  const { value: activePromotion, isLoading } = getStringFlag(
    Consts.stringFeatureFlags.ACTIVE_PROMOTION
  )

  if (isLoading || !activePromotion) return null

  return <Banner type={activePromotion} />
}

Enter fullscreen mode Exit fullscreen mode

No dashboard do ConfigCat, você configura o valor da flag como 'black-friday', 'cyber-monday' ou string vazia (desligado). Zero deploy pra trocar a campanha ativa.


User Targeting — A Mágica de Verdade

A parte mais poderosa das feature flags é o targeting. Com os dados do usuário sincronizados no ConfigCatSync, você pode criar regras no dashboard como:

Targeting por email (testar com seu próprio usuário)

IF User.email IS ONE OF ["dev@empresa.com"]
THEN serve: ON
To everyone else: OFF

Enter fullscreen mode Exit fullscreen mode

Imagem de flag com Targeting por email

Targeting por atributo (liberar pra admins)

IF User.is_admin EQUALS "1"
THEN serve: ON
To everyone else: OFF

Enter fullscreen mode Exit fullscreen mode

Rollout por porcentagem

10% dos usuários: ON
90% dos usuários: OFF

Enter fullscreen mode Exit fullscreen mode

Isso permite um rollout gradual: comece com 10%, monitore, aumente pra 50%, e finalmente 100%.

Imagem de flag com Targeting por atributo e Rollout por porcentagem


Testes

Mock do ConfigCat para Jest

Como o ConfigCat é uma dependência externa, precisamos mocká-lo nos testes. O mock deve cobrir todos os símbolos usados pelo featureFlagsContext.tsx:

// __mocks__/configcat-react.js
module.exports = {
  ConfigCatProvider: ({ children }) => children,
  useConfigCatClient: () => ({
    getValueAsync: (key, defaultValue) => Promise.resolve(defaultValue),
    setDefaultUser: () => {},
    clearDefaultUser: () => {},
    on: () => {},
    off: () => {}
  }),
  User: class User {
    constructor() {}
  }
}

Enter fullscreen mode Exit fullscreen mode

Configurar no jest.config.js

module.exports = {
  moduleNameMapper: {
    '^configcat-react$': '<rootDir>/__mocks__/configcat-react.js'
  }
}

Enter fullscreen mode Exit fullscreen mode

Mock do Context nos testes de componentes

Na prática, a maioria dos testes de componentes não precisa exercitar o Provider inteiro — basta mockar o Context diretamente. Inclua getStringFlag junto com getFlag:

jest.mock('providers/feature-flags/featureFlagsContext', () => ({
  FeatureFlagsContext: {
    _currentValue: {
      flags: {},
      getFlag: () => ({ isEnabled: true, isLoading: false }),
      getStringFlag: () => ({ value: 'default', isLoading: false })
    }
  }
}))

Enter fullscreen mode Exit fullscreen mode

Isso faz com que todas as flags boolean retornem true e as string retornem 'default' nos testes — comportamento padrão que geralmente você quer ao testar o caminho feliz.

Para testar o cenário com flag desligada:

jest.mock('providers/feature-flags/featureFlagsContext', () => ({
  FeatureFlagsContext: {
    _currentValue: {
      flags: {},
      getFlag: () => ({ isEnabled: false, isLoading: false }),
      getStringFlag: () => ({ value: '', isLoading: false })
    }
  }
}))

Enter fullscreen mode Exit fullscreen mode

Para testar o estado de carregamento:

jest.mock('providers/feature-flags/featureFlagsContext', () => ({
  FeatureFlagsContext: {
    _currentValue: {
      flags: {},
      getFlag: () => ({ isEnabled: false, isLoading: true }),
      getStringFlag: () => ({ value: '', isLoading: true })
    }
  }
}))

Enter fullscreen mode Exit fullscreen mode


Casos de Uso Reais

1. Deploy Seguro

Você desenvolveu uma feature nova. Em vez de esperar pra fazer merge só quando estiver 100% pronta:

  1. Suba o código com a flag OFF
  2. No dashboard, ligue apenas pro seu email
  3. Teste em produção com dados reais
  4. Quando estiver pronto, ligue pra todos

Zero conflito de branch. Zero rollback.

2. Kill Switch

Feature causando problemas em produção?

  1. Abra o dashboard do ConfigCat
  2. Desligue a flag
  3. Em até uma hora (ou o intervalo de polling configurado), a feature desaparece

Sem deploy. Sem rollback. Sem estresse.

3. Apresentação para Stakeholders

Quer mostrar uma feature nova só pros diretores antes de liberar?

  1. Crie uma regra de targeting com os emails dos diretores
  2. Ligue a flag
  3. Eles veem a feature, o resto dos usuários não

4. Rollout Gradual

Feature nova que pode impactar performance?

  • Dia 1: 10% dos usuários
  • Dia 3: 50% dos usuários
  • Dia 5: 100% dos usuários

Se em qualquer momento algo der errado, volte pra 0%.

5. Campanha ou Conteúdo Dinâmico (flag string)

Você tem um banner que muda conforme a campanha ativa. Em vez de fazer deploy a cada troca:

  1. Crie uma flag string activePromotion no ConfigCat
  2. Configure o valor como 'black-friday' quando a campanha começa
  3. Mude para '' (vazio) quando terminar

O componente reage automaticamente quando o polling detecta a mudança — sem deploy, sem redeploy, sem acionar o time de infra.


Boas Práticas

1. Nomenclatura consistente

Use camelCase nas flags (padrão do ConfigCat) e UPPER_SNAKE_CASE nas constantes:

// ✅ Bom — claro e descritivo
featureFlags = {
  SHOW_DASHBOARD: 'showDashboard',
  ENABLE_NEW_CHECKOUT: 'enableNewCheckout'
}

stringFeatureFlags = {
  ACTIVE_PROMOTION: 'activePromotion'
}

// ❌ Ruim — ambíguo
featureFlags = {
  DASH: 'dash',
  CHECKOUT: 'checkout'
}

Enter fullscreen mode Exit fullscreen mode

2. Escolha o tipo certo de flag

Use boolean quando... Use string quando...
Ligar/desligar uma feature Controlar qual variante exibir
Proteger uma rota Selecionar um tema ou layout
Feature em rollout gradual A/B testing com múltiplas variantes
Kill switch de emergência Conteúdo dinâmico (banners, campanhas)

3. Limpeza de flags antigas

Flags são código temporário. Quando a feature estiver 100% liberada e estável:

  1. Remova a flag do código (o if com getFlag ou o componente com getStringFlag)
  2. Remova a constante do featureFlags ou stringFeatureFlags
  3. Delete a flag do dashboard do ConfigCat

Flag esquecida é dívida técnica.

4. Comportamento seguro por padrão

O defaultFlagState retorna isEnabled: false e o defaultStringFlagState retorna value: ''. Se o ConfigCat estiver fora do ar ou demorar pra responder, nenhuma feature nova aparece — o usuário vê a versão estável da aplicação.

5. Use isLoading em páginas protegidas

Sempre cheque isLoading antes de tomar decisões como redirect:

// ✅ Bom — espera carregar antes de decidir
const { isEnabled, isLoading } = getFlag(Consts.featureFlags.SHOW_DASHBOARD)

useEffect(() => {
  if (!isLoading && !isEnabled) {
    push('/app')
  }
}, [isLoading, isEnabled, push])

// ❌ Ruim — redireciona antes de saber o valor real
const { isEnabled } = getFlag(Consts.featureFlags.SHOW_DASHBOARD)

useEffect(() => {
  if (!isEnabled) {
    push('/app') // Vai redirecionar no primeiro render!
  }
}, [isEnabled, push])

Enter fullscreen mode Exit fullscreen mode


Vantagens da Arquitetura

Desacoplamento total

// Componentes não conhecem ConfigCat
import { FeatureFlagsContext } from 'providers/feature-flags/featureFlagsContext'

const { getFlag, getStringFlag } = useContext(FeatureFlagsContext)

Enter fullscreen mode Exit fullscreen mode

Fácil migração

Para trocar ConfigCat por outra ferramenta (ex: LaunchDarkly), você altera apenas o ConfigCatSync e o FeatureFlagsProvider. Os componentes continuam usando useContext(FeatureFlagsContext) + getFlag/getStringFlag — não mudam nada.

Estado centralizado

Todas as flags são buscadas uma vez e compartilhadas via Context. Se 10 componentes usam a mesma flag, o ConfigCat é consultado uma vez só — não 10 vezes.

Reatividade automática

O listener configChanged garante que quando uma flag muda no dashboard, todos os componentes que a consomem atualizam automaticamente, sem refresh da página. Isso vale tanto para flags boolean quanto string.

Dois tipos de flag, um contrato unificado

O Context expõe getFlag e getStringFlag com contratos claros e tipados. Componentes nunca lidam com any ou precisam fazer cast — o TypeScript guia o uso correto desde o autocompletar.


Conclusão

Feature Flags transformaram a forma como fazemos deploy. Com ConfigCat como engine e React Context API como camada de abstração, conseguimos:

  • ✅ Deploy contínuo sem medo
  • ✅ Testes em produção com segurança
  • ✅ Kill switch instantâneo
  • ✅ Targeting avançado por usuário
  • ✅ Suporte a flags boolean e string
  • ✅ Estado centralizado e reativo
  • ✅ Arquitetura desacoplada — pronta pra trocar de ferramenta
  • ✅ TypeScript garantindo type safety nas flags

O investimento de criar a camada de abstração com Context API vale muito a pena. O código fica limpo, testável e preparado pro futuro.


Indo Além — O que este guia não cobre

O que implementamos aqui é uma base sólida e pronta pra produção. Mas feature flags como conceito têm muito mais profundidade. Se você quiser continuar explorando:

Outros tipos de valor
Além de boolean e string, ferramentas como ConfigCat suportam flags do tipo int, double e até JSON. Isso abre espaço para casos como configurar limites dinâmicos (rate limit, tamanho de paginação, timeout de requisição) ou passar objetos de configuração inteiros sem deploy.

Flags no servidor
A implementação deste artigo é inteiramente client-side. Em aplicações Next.js com uso intenso de SSR ou Server Components, você pode — e muitas vezes deve — avaliar flags no servidor, antes de renderizar a página. Isso evita flashes de conteúdo (o usuário nunca vê o estado "carregando") e permite decisões de roteamento ainda na borda.

Testes com flags em múltiplos estados no CI
Uma flag desligada em produção não significa que o código por trás dela está livre de bugs. Estratégias de CI/CD mais avançadas rodam o pipeline com a flag ligada e desligada, garantindo que ambos os caminhos sejam validados antes do merge.

Gestão do ciclo de vida das flags
Flags acumuladas viram dívida técnica silenciosa. Existem abordagens para rastrear a idade de cada flag, definir uma data de expiração desde a criação, e automatizar alertas quando uma flag passa do prazo esperado — antes que ela vire um if esquecido no código.

Plataformas de experimentação
Feature flags são o passo zero de um experimento. Ferramentas como LaunchDarkly, Split.io e GrowthBook vão além: medem o impacto estatístico de cada variante, calculam significância e ajudam a tomar decisões de produto baseadas em dados — não em intuição.

Cada um desses tópicos rende um artigo próprio. O importante é que a arquitetura que construímos aqui já está preparada para evoluir em qualquer dessas direções — o desacoplamento via Context API garante isso.


Recursos


Escrito por Guilherme Marucchi — Meu LinkedIn