Server Components vs Client Components en Next.js : la bonne architecture"
Server Components vs Client Components en Next.js. Comment bien les utiliser, les pièges courants, et les bonnes pratiques pour garder ton app rapide."
Server Components vs Client Components : choisir le bon
Next.js 13+ change la donne avec les Server Components. Mais beaucoup de devs confondent encore quand utiliser quoi. C'est ton choix le plus important pour la perf. Mauvais choix = site lent et bundle énorme. Bon choix = le reste suit naturellement.
Pourquoi les Server Components d'abord
Par défaut, tout doit être un Server Component dans Next.js 13+. C'est contre-intuitif quand tu viens de React classique, mais c'est la logique nouvelle : le serveur prépare le HTML, le client reçoit du contenu à peu près fini.
Les Server Components ne génèrent aucun JavaScript côté client. Zéro. Ça revient à écrire du PHP côté rendu, mais avec React.
Compare :
Sans Server Components (old React) :
Tu charges React en JS : ~42 KB gzippé
Tu envoies le composant en JS au client
Le client hydrate, parse, exécute
Résultat : lent, surtout sur mobile
Avec Server Components (new Next.js) :
Le serveur rend le composant en HTML
Tu envoies du HTML, pas du JS
Pas d'hydratation coûteuse
Résultat : rapide d'emblée
server-component.tsxTSX / React
// ✅ Server Component (défaut)export default async function Article({ id }) { const data = await fetch(`/api/articles/${id}`).then(r => r.json()); return <div>{data.title}</div>;}// ❌ Problème : impossible d'utiliser useState, onClick, etc.
Si tu as besoin d'interactivité (onclick, formulaires, état), tu passes en Client Component avec la directive "use client" en haut du fichier.
Quand vraiment utiliser Client Components
Tu utilises un Client Componentuniquement quand c'est nécessaire :
Liste de critères
Tu as du useState ou useEffect
Tu écoutes des événements : onClick, onChange, etc.
Tu utilises un hook custom qui ne peut pas être côté serveur
Tu accèdes à window ou localStorage
Autre chose ? Faux besoin — reste en Server Component.
Beaucoup mettent "use client" en haut d'une page juste pour un bouton. Résultat : la page entière devient client, et tu perds les avantages. La structure à viser :
architecture.tsx
1
-
"use client";
1
+
// Pas de "use client" : Page reste serveur
2
2
3
3
export default function Page() {
4
-
// Toute la page en client 😞
5
4
return (
6
5
<div>
7
-
<Header />
8
-
<Content />
9
-
<Counter /> {/* Seul ça a besoin du client */}
6
+
<Header /> {/* Server Component */}
7
+
<Content /> {/* Server Component */}
8
+
<Counter /> {/* Client Component, isolé */}
10
9
</div>
11
10
);
11
+
}
12
+
13
+
// En bas du fichier ou fichier séparé :
14
+
"use client";
15
+
16
+
function Counter() {
17
+
// Juste le bouton est client
18
+
return <button>Click</button>;
12
19
}
AvantAprès
+12-5
1
"use client";
2
3
export default function Page() {
4
// Toute la page en client 😞
5
return (
6
<div>
7
<Header />
8
<Content />
9
<Counter /> {/* Seul ça a besoin du client */}
10
</div>
11
);
12
}
1
// Pas de "use client" : Page reste serveur
2
3
export default function Page() {
4
return (
5
<div>
6
<Header /> {/* Server Component */}
7
<Content /> {/* Server Component */}
8
<Counter /> {/* Client Component, isolé */}
9
</div>
10
);
11
}
12
13
// En bas du fichier ou fichier séparé :
14
"use client";
15
16
function Counter() {
17
// Juste le bouton est client
18
return <button>Click</button>;
19
}
Données : fetch côté serveur, pas axios côté client
Grave erreur : utiliser axios ou fetch dans un useEffect client. Tu envoies les données deux fois (une au serveur, une au client), tu hydrates mal, et le data est undefined au premier rendu.
La bonne façon
Fetch les données côté serveur avant de rendre, puis passe-les au client si besoin :
data-fetching.tsxTSX / React
// page.tsx — Server Componentexport default async function Page() { const articles = await fetch( `${process.env.NEXT_PUBLIC_API}/articles` ).then(r => r.json()); return ( <div> <ArticleList articles={articles} /> {/* Passe les données */} </div> );}// components/ArticleList.tsx — peut être Client Component"use client";export default function ArticleList({ articles }) { const [filter, setFilter] = useState(""); // Utilise articles directement, pas besoin de refetch return ( <div> <input onChange={(e) => setFilter(e.target.value)} /> {articles.filter(a => a.title.includes(filter)).map(a => ( <div key={a.id}>{a.title}</div> ))} </div> );}
“”
Server Components d'abord, Client Components ensuite. Pas l'inverse. 80% de ton app doit rester serveur si tu veux vraiment optimiser.
Checklist avant deploy
Zéro "use client" sur les pages entières, sauf vraiment besoin
Les useState et useEffect sont isolés dans des petits composants
Les fetches sensibles (base de données, API keys) sont côté serveur
Tu passes les données du serveur au client en props, pas en refetch
Ton bundle client (vérifier avec @next/bundle-analyzer) reste sous 200 KB
package.jsonTerminal / Bash
// Ajoute au package.json pour voir la taille du bundle"analyze": "ANALYZE=true next build"// Puis lance :// pnpm analyze
Puis regarde quels packages occupent le plus de place et vois si tu peux les remplacer.