Conseils4 MIN DE LECTURE

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."

Ordinateur portable ouvert dans le noir, clavier et écran éclairés de néons rose et bleu, avec le titre « La minute code »
Sommaire

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 Component uniquement quand c'est nécessaire :

Liste de critères

  1. Tu as du useState ou useEffect
  2. Tu écoutes des événements : onClick, onChange, etc.
  3. Tu utilises un hook custom qui ne peut pas être côté serveur
  4. Tu accèdes à window ou localStorage
Autre chose ? Faux besoin — reste en Server Component.
client-component.tsxTSX / React
"use client";

import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      Clique : {count}
    </button>
  );
}

L'erreur classique : Server Components trop loin

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
22  
33 export default function Page() {
4- // Toute la page en client 😞
54 return (
65 <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é */}
109 </div>
1110 );
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>;
1219 }

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 Component
export 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

  1. Zéro "use client" sur les pages entières, sauf vraiment besoin
  2. Les useState et useEffect sont isolés dans des petits composants
  3. Les fetches sensibles (base de données, API keys) sont côté serveur
  4. Tu passes les données du serveur au client en props, pas en refetch
  5. 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.

Partager
À lire aussi
08Deux formules, un appel

30 minutes pour décider
si on travaille ensemble.

Réserver mon appel