Retour au blog

React Server Components vs Client Components

15 janvier 20252 min de lecture
ReactNext.jsRSC

Deux modèles de composants

  • **Server Components** : rendus sur le serveur, pas de `useState` ni d’effets, mais on peut `await` directement une requête base de données ou un `fetch` sans passer par un `useEffect`.
  • **Client Components** : hydratés côté navigateur, peuvent utiliser les hooks React et réagir aux événements (`onClick`, `onChange`, etc.).

Par défaut dans l’App Router, tout composant est un Server Component. On passe en Client Component uniquement en ajoutant `"use client"` en haut du fichier.

Quand choisir l’un ou l’autre

La question à se poser n’est pas « lequel est le meilleur » mais « qu’est-ce que ce composant a réellement besoin de faire côté navigateur ». Un Client Component devient nécessaire dès qu’on a besoin de :

  • **State et interactivité** : `useState`, `useEffect`, gestion de formulaire côté client, animations pilotées par JS.
  • **API du navigateur** : `localStorage`, `IntersectionObserver`, géolocalisation, etc.
  • **Librairies qui dépendent du DOM** : la plupart des libs de charts, de drag-and-drop, ou d’animation avancée.

Tout le reste — afficher des données, composer de la mise en page, formatter du texte — peut rester en Server Component, et c’est presque toujours préférable : moins de JavaScript envoyé au navigateur, rendu plus rapide, pas de flash de contenu non hydraté.

L’erreur la plus fréquente

Le piège classique est de mettre `"use client"` trop haut dans l’arbre de composants. Un seul bouton interactif dans une page suffit souvent à faire basculer accidentellement toute la page (et tout ce qu’elle importe) côté client. La bonne pratique est d’isoler la partie interactive dans son propre petit composant, et de garder tout le reste — layout, contenu, récupération de données — en Server Component autour.

Exemple avec App Router

Un composant serveur par défaut, qui récupère les données et délègue l’interactivité :

// app/page.tsx
import Posts from "./posts"

export default async function Home() {
  const posts = await getPosts()
  return <Posts posts={posts} />
}

Et un Client Component minimal, uniquement pour la partie qui a vraiment besoin d’interactivité :

"use client"
import { useState } from "react"

export function Posts({ posts }: { posts: Post[] }) {
  const [query, setQuery] = useState("")
  const filtered = posts.filter((p) =>
    p.title.toLowerCase().includes(query.toLowerCase())
  )

  return (
    <>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        className="mb-4 rounded-md border border-border px-3 py-2 text-sm"
        placeholder="Rechercher un article…"
      />
      <ul className="space-y-2">
        {filtered.map((p) => (
          <li key={p.id}>{p.title}</li>
        ))}
      </ul>
    </>
  )
}

Conclusion

Bien séparer Server et Client permet de réduire le JavaScript envoyé et d’améliorer les performances. Le réflexe à garder : composer par défaut en Server Components, et ne descendre en Client Component que pour la portion précise qui en a vraiment besoin.