Deux mondes
Dans l'App Router, tout composant est un Server Component par défaut. Il s'exécute sur le serveur (au build ou à la requête), n'envoie aucun JavaScript au navigateur et peut lire des fichiers, une base de données ou des secrets.
Un Client Component, déclaré par "use client", est envoyé au navigateur. Il est nécessaire dès que l'on utilise de l'état, des effets ou des événements.
Comment choisir
| Besoin | Type de composant |
|---|---|
| Lire des données, fichiers, secrets | Serveur |
| Rendu lourd (Markdown, coloration) | Serveur |
useState, useEffect, onClick | Client |
API du navigateur (localStorage, matchMedia) | Client |
La règle : restez sur le serveur tant que l'interactivité ne l'impose pas, et descendez la frontière client le plus bas possible dans l'arbre.
Étude de cas : les présentations
Une présentation est un tableau de diapositives navigable au clavier. La navigation est interactive, donc SlideShow est un Client Component. Mais chaque diapositive contient du Markdown, avec de la coloration de code faite par Shiki.
La mauvaise idée serait de rendre le Markdown dans SlideShow :
"use client";
// ❌ Shiki et react-markdown finiraient dans le bundle navigateur
export function SlideShow({ slides }: { slides: string[] }) {
return slides.map((s) => <MdxRenderer source={s} />);
}La bonne approche est de rendre chaque diapositive côté serveur, puis de passer des nœuds React déjà rendus au composant client :
// page.tsx (serveur)
const slides = parseSlides(content).map((s) => <MdxRenderer source={s} />);
return <SlideShow slides={slides} />;Le composant client reçoit du JSX prêt à afficher et ne s'occupe que de la navigation.
Un piège : les composants asynchrones
CodeBlock est un composant asynchrone (il attend Shiki). Un Client Component ne sait pas en monter un : le code des diapositives aurait cassé. La frontière serveur/client doit donc aussi tenir compte de l'asynchronisme.
Ce que cela donne en pratique
- Le code client du blog pèse environ 7 Ko (thème et diaporama).
- Le Markdown, Shiki et react-markdown ne quittent jamais le serveur.
- Mermaid, lui, est client mais chargé à la demande, uniquement sur les pages qui ont un diagramme.
À retenir
- Serveur par défaut, client par nécessité.
- On passe du JSX rendu en
props, pas des données à rendre. - Une frontière bien placée est la meilleure optimisation de performance.