Un feature flag pour une fonctionnalité qui n'existe pas encore
Foodproof est gratuit aujourd’hui. Pas “gratuit avec un plan payant caché quelque part”, vraiment gratuit, tout est débloqué. Et pourtant, le code du paywall est déjà présent dans l’app que tu peux télécharger sur l’App Store en ce moment. Voici pourquoi, et comment c’est fait.
Un seul booléen
Tout tient dans un fichier de quatre lignes :
// src/config/features.ts
export const FEATURES = {
SUBSCRIPTION_ENABLED: false,
} as const;
Ce flag est lu directement dans cinq écrans de l’app, à chaque endroit où une limitation ou un badge premium pourrait apparaître :
{FEATURES.SUBSCRIPTION_ENABLED && isPremium && (
<PremiumBadge />
)}
{FEATURES.SUBSCRIPTION_ENABLED && !isPremium && history.length > maxHistoryItems && (
<UpgradePrompt />
)}
Tant que SUBSCRIPTION_ENABLED vaut false, ces blocs ne s’affichent jamais. Le code existe, tourne dans les mêmes builds que tout le monde télécharge, mais ne produit aucun rendu. Zéro risque de montrer un paywall à moitié fini à un utilisateur.
Le détail qui compte : rien n’est branché derrière
Ce qui rend cette histoire honnête plutôt que juste une astuce de config, c’est que RevenueCat (le service qu’on prévoit d’utiliser pour gérer les abonnements) n’est même pas intégré. Pas de react-native-purchases dans les dépendances, aucun import RevenueCat nulle part dans le code.
Le store qui devrait porter le statut d’abonnement de l’utilisateur a un statut… hardcodé :
const useSubscriptionStore = create<SubscriptionState>()(
persist(
(set) => ({
isPremium: true, // TODO: brancher RevenueCat ici
plan: "free",
// ...
})
)
);
isPremium: true pour tout le monde, avec un commentaire qui dit clairement que ce n’est pas fini. Et comme le flag global est à false, cette valeur ne change rien à l’expérience actuelle : les blocs de code qui la consultent ne s’affichent pas de toute façon. C’est un placeholder qui attend son heure.
Pourquoi construire ça avant d’avoir un moyen de payer
L’alternative aurait été de laisser le code du paywall dans une branche à part, et de tout fusionner d’un coup le jour où RevenueCat serait prêt. Sur un projet solo, cette option a un défaut concret : plus une branche vit longtemps en parallèle de main, plus elle diverge, et plus la fusion finale devient un moment risqué où tout peut casser en même temps.
En développant le paywall directement dans les mêmes builds que le reste, derrière un flag qui l’éteint proprement, chaque écran qui touche à l’UI premium est testé en continu, dans le même contexte que le code qui l’entoure. Le jour où RevenueCat sera branché, il ne restera que deux choses à faire : remplacer la valeur hardcodée de isPremium par le vrai statut d’abonnement, et passer SUBSCRIPTION_ENABLED à true. Pas de fusion de branche géante, pas de moment où personne n’est sûr que tout marche encore.
C’est une décision produit autant que technique : livrer l’infrastructure avant la fonctionnalité, pour que le jour où la fonctionnalité arrive, ce ne soit plus qu’un interrupteur à basculer.