Les 4 patterns React qui survivent à 18 mois de croissance produit (et les 3 qui s'effondrent)
Un projet React propre peut devenir un enchevêtrement en quelques mois. Voici les 4 patterns de composition qui tiennent à la croissance — et les 3 qui s'effondrent à coup sûr, avec React 19.
Pourquoi l'architecture décide de la durée de vie d'un projet
Un projet React peut démarrer impeccable et se transformer en enchevêtrement en quelques mois. La différence entre les deux trajectoires tient rarement au talent des développeurs — elle tient aux patterns choisis au départ. Après avoir accompagné des dizaines d'équipes sur des projets React de toutes tailles, je vois les mêmes erreurs structurelles se répéter, et les mêmes patterns sauver la mise.
React 19, stable depuis décembre 2024 et en version 19.2 en 2026, a renforcé cette dynamique plutôt que de la bouleverser. Les Server Components sont stables, les Actions simplifient les formulaires, l'API use() lit des Promises pendant le rendu, et le React Compiler rend la mémoïsation manuelle de moins en moins nécessaire. Aucune de ces évolutions ne périme les fondamentaux : elles les renforcent. Voici les quatre patterns qui passent le test des 18 mois de croissance — et les trois qui s'effondrent à coup sûr.
Les 4 patterns qui survivent
1. Le container serveur / presenter client (RSC)
Les React Server Components ont ressuscité et modernisé le vieux pattern container-presenter. Les composants serveur jouent les containers : ils récupèrent les données, portent la logique métier, préparent les props. Les composants client, marqués 'use client', jouent les presenters : ils reçoivent des données sérialisables et gèrent l'interactivité. La frontière de sérialisation entre serveur et client impose une discipline bénéfique — pas de fonctions ni de classes qui la traversent — et force une séparation nette des responsabilités. Les Server Actions complètent le modèle, et avec useActionState et useFormStatus, un formulaire complet s'écrit en une fraction du code d'avant.
"use client";
import { useActionState } from "react";
import { subscribe } from "./actions"; // 'use server'
export function SignupForm() {
const [state, formAction, pending] = useActionState(subscribe, null);
return (
<form action={formAction}>
<input name="email" type="email" required />
<button disabled={pending}>{pending ? "…" : "S'abonner"}</button>
{state?.error && <p role="alert">{state.error}</p>}
</form>
);
}2. Les Compound Components
C'est mon outil de composition préféré pour les interfaces complexes : un ensemble de composants qui partagent un état implicite via Context. Plutôt qu'un Select monolithique à vingt props, tu exposes Select.Trigger, Select.Content, Select.Item. Le consommateur réorganise, omet ou intercale des sous-composants sans que le parent ait à prévoir tous les cas. C'est la composition dans sa forme la plus pure. React 19 simplifie d'ailleurs l'implémentation puisque ref est devenu une prop régulière — fini forwardRef pour faire passer une référence à travers les couches, un vrai gain de lisibilité dans les hiérarchies profondes.
3. Le Headless UI
Le pattern headless dissocie complètement la logique d'interaction de son rendu : gestion du focus, navigation clavier, états ARIA d'un côté ; aucun style imposé. Le consommateur fournit l'apparence, ce qui met fin aux batailles avec le CSS d'un design system tiers. L'écosystème 2026 est mûr : Radix UI Primitives (maintenu par WorkOS) sert de fondation à shadcn/ui et est devenu un standard de fait ; React Aria d'Adobe offre une couverture d'accessibilité exemplaire ; Ark UI s'appuie sur des machines à états via Zag.js. shadcn/ui mérite une mention à part : au lieu d'installer une dépendance npm, il génère le code dans ton projet via npx shadcn@latest add — attention, la CLI a été renommée de shadcn-ui à shadcn, et sa version 4 (mars 2026) scaffold désormais des templates complets. Tu possèdes le code, tu le modifies librement, aucune contrainte de mise à jour.
4. La colocation et les hooks custom
Pour l'organisation, j'applique la colocation : chaque fonctionnalité regroupe ses composants, hooks, types et tests dans un même dossier. Cette approche feature-based rend les dépendances explicites et les refactorings locaux. Et je suis la règle du composant court : au-delà de 50 à 100 lignes, j'extrais de la logique dans un hook custom ou je décompose. Les hooks personnalisés sont le mécanisme de réutilisation le plus puissant de React — un composant qui se contente de composer des hooks et de rendre du JSX se comprend, se teste et se maintient pendant des années. Le React Compiler renforce ce pattern en prenant en charge la mémoïsation, ce qui allège encore le code.
Les 3 patterns qui s'effondrent
1. Le god component et le prop drilling
Le composant qui grossit jusqu'à porter vingt props et trois cents lignes est la première cause de dette. Il devient impossible à tester, à réutiliser, à comprendre. Son cousin, le prop drilling — faire descendre une prop à travers cinq niveaux de composants qui ne s'en servent pas — signale presque toujours qu'un Compound Component ou un Context aurait dû être employé. Ces deux-là survivent rarement à six mois, encore moins à dix-huit.
2. L'état global prématuré
L'erreur classique : tout mettre dans un store global « au cas où ». En 2026, le paysage a tranché. Redux est en déclin pour les nouveaux développements ; Zustand s'est imposé avec plus de 40 millions de téléchargements npm mensuels et une API minimale ; Jotai couvre l'état atomique ; et surtout, TanStack Query domine l'état serveur avec son cache intelligent. La conséquence pratique : la plupart de ce qu'on mettait dans Redux est en réalité de l'état serveur, qui appartient à TanStack Query, ou de l'état local, qui appartient au composant. Le store global est le dernier recours, pas le premier réflexe.
3. L'organisation par type technique
Un dossier components, un dossier hooks, un dossier utils : ça paraît propre au début, et ça devient ingérable passé une certaine taille, parce que travailler sur une fonctionnalité oblige à sauter entre cinq dossiers. C'est l'anti-pattern qui s'effondre le plus silencieusement — personne ne le remarque jusqu'au jour où ajouter une feature touche trente fichiers éparpillés. La colocation feature-based règle le problème dès le départ.
| Pattern | Verdict | Pourquoi |
|---|---|---|
| Container serveur / presenter client | survit | séparation imposée par la sérialisation |
| Compound Components | survit | composition flexible, état implicite |
| Headless UI (Radix, shadcn) | survit | logique et apparence découplées |
| Colocation + hooks custom | survit | dépendances explicites, refactor local |
| God component / prop drilling | s'effondre | intestable, illisible |
| État global prématuré | s'effondre | confond état serveur et état local |
| Organisation par type technique | s'effondre | feature éclatée sur 5 dossiers |
Le fil conducteur de ces sept cas est le même : les patterns qui survivent imposent une séparation claire des responsabilités, et ceux qui s'effondrent la diluent. React 19 et son outillage te donnent de meilleurs outils pour faire le bon choix, mais ils ne le feront pas à ta place. Le test des 18 mois ne se gagne pas avec le dernier framework à la mode — il se gagne avec de la discipline architecturale, prise dès le premier commit.
Du token Figma au composant React en 5 étapes — et la moitié de la glue d'avant ne sert plus à rien
Le pipeline token→composant que tout le monde recopie est à moitié périmé. Depuis Tailwind v4 et la spec Design Tokens stable, voici les 5 étapes qui comptent vraiment pour passer de Figma à React.
Pourquoi tes animations React rament à 30 fps — et le réflexe Framer Motion qui corrige ça
Une animation qui saccade vient presque toujours d'une propriété mal choisie. Le réflexe qui corrige ça, et les patterns Motion (ex-Framer Motion v12) pour des interfaces fluides à 60 fps.
15 micro-interactions CSS sans une ligne de JavaScript (ce qui exigeait une lib hier)
Le soulignement qui glisse, la carte qui bascule en 3D, l'entrée animée sans display: none bricolé : 15 micro-interactions en CSS pur, à 60 fps, sans une ligne de JavaScript.