Documentation

Feature flags

Activation progressive de features par toggle global, % de rollout déterministe ou allowlist d'users.

Système de feature flags simple, en DB, sans dépendance externe (GrowthBook, LaunchDarkly…). 3 leviers combinables par flag.

Les 3 leviers

LevierEffet
enabled: booleanToggle global on/off
rolloutPct: 0-100% d'users qui voient la feature, déterministe par userId
userIds: string[]Allowlist explicite — toujours actif même si enabled=false

Le bucketing du rolloutPct utilise sha256(key + userId) % 100 → un user voit toujours le même résultat (pas de flicker entre requêtes), et à 25 % ce sont toujours les mêmes 25 %.

Cas d'usage typiques

BesoinConfig
Activer pour tousenabled=true, rolloutPct=100
Toi seul (dev)enabled=false, userIds=["cuid_du_user"]
10 % des usersenabled=true, rolloutPct=10
Équipe interne + 25 % publicenabled=true, rolloutPct=25, userIds=[...team]
Kill-switchenabled=false, userIds=[]

Utilisation côté code

import { isFeatureEnabled } from "@/lib/feature-flags";

const session = await auth();
const showNewDashboard = await isFeatureEnabled("new-dashboard", session?.user?.id);

if (showNewDashboard) return <NewDashboard />;
return <OldDashboard />;

Le helper est cached via unstable_cache (60 s + tag feature-flags). Les routes admin appellent revalidateTag("feature-flags") à chaque modification.

Anonyme (userId = null) : seul enabled=true && rolloutPct=100 les active. Logique car on ne peut pas bucket un user inconnu.

Admin — créer / modifier un flag

Page /admin/feature-flags :

  • Bouton Nouveau flag → modal (key + description)
  • Sur chaque flag : toggle enabled, slider rollout %, suppression
  • (Allowlist userIds éditable via API directement, pas encore en UI)

Routes API

GET    /api/admin/feature-flags        → liste tous les flags
POST   /api/admin/feature-flags        → create { key, description?, enabled, rolloutPct, userIds }
PATCH  /api/admin/feature-flags/[id]   → update partiel
DELETE /api/admin/feature-flags/[id]

Toutes admin-only (requireApiAuth({ admin: true })) avec audit log (feature_flag.create / update / delete).

Modèle Prisma

model FeatureFlag {
  id          String   @id @default(cuid())
  key         String   @unique
  description String?
  enabled     Boolean  @default(false)
  rolloutPct  Int      @default(0)
  userIds     String[] @default([])
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}

Scaling

Le cache est mémoire-process. En multi-instance (Vercel serverless), chaque instance a son propre cache de 60 s — un toggle peut prendre jusqu'à 60 s à se propager partout. Pour un kill-switch instantané, appelle revalidateTag("feature-flags") partout (ou passe à Redis).

On this page