Architecture React : Patterns et bonnes pratiques
Les patterns React essentiels pour construire des applications maintenables et scalables.
Structurer un projet React comme un pro
Après 10+ projets React, j'ai identifié les patterns qui font la différence entre un projet maintenable et un plat de spaghetti. Voici mon approche éprouvée.
1. La structure de dossiers "Feature-First"
Oubliez l'organisation par type de fichier (components/, hooks/, utils/). Préférez l'organisation par fonctionnalité :
src/ ├── features/ │ ├── auth/ │ │ ├── components/ │ │ ├── hooks/ │ │ ├── services/ │ │ └── index.ts │ ├── dashboard/ │ │ ├── components/ │ │ ├── hooks/ │ │ └── index.ts │ └── projects/ ├── shared/ │ ├── components/ # Boutons, modals, inputs... │ ├── hooks/ # useDebounce, useLocalStorage... │ └── utils/ # formatDate, cn()... └── app/ ├── routes.tsx └── providers.tsx
Pourquoi ça marche ?
Chaque feature est un module autonome. Si vous devez supprimer la feature "blog", vous supprimez un dossier. Pas besoin de chasser des fichiers dans 15 dossiers différents.
2. Le pattern Compound Components
Au lieu de passer 20 props à un composant, utilisez le pattern compound :
tsx// ❌ Trop de props, illisible <Card title="Mon projet" description="Un super projet" image="/img.jpg" actions={[{ label: 'Voir', onClick: () => {} }]} footer="2025" variant="outlined" /> // ✅ Compound Components — clair et flexible <Card> <Card.Image src="/img.jpg" /> <Card.Body> <Card.Title>Mon projet</Card.Title> <Card.Description>Un super projet</Card.Description> </Card.Body> <Card.Footer> <Card.Action onClick={() => {}}>Voir</Card.Action> </Card.Footer> </Card>
Implémentation :
tsxconst CardContext = createContext<{ variant?: string }>({}); function Card({ children, variant = 'default' }) { return ( <CardContext.Provider value={{ variant }}> <div className="card">{children}</div> </CardContext.Provider> ); } Card.Title = ({ children }) => <h3 className="card-title">{children}</h3>; Card.Body = ({ children }) => <div className="card-body">{children}</div>; Card.Image = ({ src }) => <img className="card-img" src={src} />;
3. Le pattern Custom Hook "Service"
Séparez votre logique métier de vos composants avec des hooks dédiés :
tsx// hooks/useProjects.ts — toute la logique ici export function useProjects() { const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { fetchProjects().then(setProjects).catch(setError).finally(() => setLoading(false)); }, []); const addProject = async (data) => { /* ... */ }; const deleteProject = async (id) => { /* ... */ }; return { projects, loading, error, addProject, deleteProject }; } // components/ProjectList.tsx — uniquement du rendu function ProjectList() { const { projects, loading } = useProjects(); if (loading) return <Skeleton />; return projects.map(p => <ProjectCard key={p.id} project={p} />); }
4. Error Boundaries stratégiques
Ne mettez pas un seul Error Boundary au niveau de l'app. Placez-les stratégiquement :
tsx<Layout> <ErrorBoundary fallback={<NavError />}> <Navbar /> </ErrorBoundary> <ErrorBoundary fallback={<ContentError />}> <Outlet /> </ErrorBoundary> <Footer /> {/* Pas d'error boundary — le footer ne crash jamais */} </Layout>
Conclusion
L'architecture n'est pas un luxe : c'est ce qui fait que votre projet survit au-delà du prototype. Adoptez ces patterns progressivement, et vous verrez la différence dès les premières itérations.
À lire ensuite
Liquid Glass : fondamentaux pour une interface lisible et cohérente
Comprendre les couches, la lumière, la profondeur et les limites du verre numérique avant d’ajouter le moindre blur.
CSSLiquid Glass en CSS : backdrop-filter accessible et robuste
Une recette CSS progressive avec tokens, fallbacks, contraste, préférences utilisateur et états clavier visibles.