Retour au cours

frontend / react

Server Components vs Client Components

Leçon 171 exercice

Explication

Ce que vous allez apprendre

  • Comprendre pourquoi tout le code React n'a pas besoin d'être exécuté côté navigateur
  • Distinguer un Server Component d'un Client Component et leurs capacités respectives
  • Utiliser la directive "use client" pour marquer la frontière entre les deux mondes
  • Composer un Server Component avec un Client Component via des props ou le slot pattern
  • Garder les secrets et accès base de données confinés au serveur

Dans quel contexte ?

Sur src/app/cours/[id]/page.jsx (Next.js App Router), un développeur doit afficher le contenu d'un cours récupéré directement en base de données, tout en gardant un bouton "Ajouter aux favoris" interactif. Faire de toute la page un Client Component obligerait à passer par une route API intermédiaire juste pour lire la base de données, alors qu'un Server Component peut y accéder directement. La solution : la page reste un Server Component, et seul le bouton favori devient un Client Component isolé avec "use client".

Étape 1 : tout s'exécutait côté navigateur jusqu'ici

Jusqu'ici dans ce cours, tout composant React était exécuté dans le navigateur de l'utilisateur : son code JavaScript devait être téléchargé, puis exécuté côté client.

Étape 2 : les deux problèmes que ça pose

Cela pose deux problèmes à grande échelle : chaque composant alourdit le bundle JavaScript, et un composant qui a besoin de données doit passer par une API intermédiaire, même sans interactivité réelle.

Étape 3 : la solution, les Server Components

Les React Server Components (RSC) sont des composants qui s'exécutent EXCLUSIVEMENT sur le serveur, et dont le code ne rejoint jamais le bundle JavaScript envoyé au navigateur.

Étape 4 : ce qu'ils gagnent, ce qu'ils perdent

Ils peuvent accéder directement à une base de données sans jamais l'exposer côté client. En contrepartie, ils ne peuvent avoir aucune interactivité : pas de useState, pas de useEffect.

Étape 5 : et pour l'interactivité, "use client"

Un Client Component est, à l'inverse, un composant classique qui s'exécute aussi dans le navigateur. La directive "use client" marque explicitement la frontière entre les deux mondes.

Étape 6 : comment les deux collaborent

Un Server Component peut englober un Client Component en lui transmettant des données déjà résolues via des props classiques, jamais une connexion à une base de données.

Un dernier cas particulier

L'inverse n'est pas possible directement : un Client Component ne peut pas importer un Server Component, sauf en le recevant via children, le "slot pattern".

Piège fréquent

Ajouter "use client" en haut d'un fichier qui importe une clé d'API secrète ou une connexion base de données expose ce code (et potentiellement ces secrets) au bundle JavaScript envoyé au navigateur. Garde toujours les accès sensibles dans des Server Components, jamais dans un fichier marqué "use client".

Besoin du composantType à choisir
Lire directement une base de donnéesServer Component
Gérer un clic, un state, un formulaireClient Component
Afficher du contenu statique sans interactionServer Component

Et ensuite ?

Une fois cette distinction comprise, la prochaine étape explore les approches modernes de récupération de données.

Commandes & code

Server Components vs Client Components

React Server Components (RSC) exécutent du code EXCLUSIVEMENT sur le serveur, sans jamais rejoindre le bundle client.

jsx
// Server Component (défaut dans un framework type Next.js App Router, pas de directive nécessaire)
// FichierListeCours.jsx -- s'exécute UNIQUEMENT côté serveur
async function ListeCours() {
    // accès direct à la base de données ou à un service interne : jamais exposé au client
    const cours = await db.cours.findMany({ where: { publie: true } });

    return (
        <ul>
            {cours.map((c) => (
                <li key={c.id}>{c.titre}</li>
            ))}
        </ul>
    );
}
// aucun useState/useEffect/onClick possible ici : pas d'interactivité, pas de bundle JS envoyé
jsx
// Client Component : directive explicite obligatoire, exécuté aussi côté navigateur
"use client";

import { useState } from "react";

function BoutonFavori({ coursId, estFavoriInitial }) {
    const [estFavori, setEstFavori] = useState(estFavoriInitial);

    async function basculer() {
        setEstFavori(!estFavori); // interactivité -> nécessite le JS client
        await fetch(`/api/favoris/${coursId}`, { method: "POST" });
    }

    return (
        <button onClick={basculer}>
            {estFavori ? "Favori" : "Ajouter aux favoris"}
        </button>
    );
}
jsx
// Composition : un Server Component peut englober un Client Component (mais pas l'inverse directement)
async function PageCours({ id }) {
    const cours = await db.cours.findUnique({ where: { id } }); // fetch côté serveur

    return (
        <article>
            <h1>{cours.titre}</h1>
            <p>{cours.description}</p>

            {/* Client Component reçoit des données déjà résolues en props, pas la connexion DB */}
            <BoutonFavori coursId={cours.id} estFavoriInitial={cours.estFavori} />
        </article>
    );
}

// Passer un Server Component EN CHILDREN d'un Client Component reste possible :
// c'est le "slot pattern", utile pour garder de l'interactivité en frontière
"use client";
function CarteAvecAnimation({ children }) {
    const [survole, setSurvole] = useState(false);
    return (
        <div onMouseEnter={() => setSurvole(true)} className={survole ? "carte--hover" : "carte"}>
            {children} {/* peut être un Server Component rendu par le parent */}
        </div>
    );
}
Server ComponentClient Component
Accès direct DB/fichiers/secretsInteractivité (state, événements)
Zéro JS envoyé au navigateurNécessite "use client"
async function autorisée directementuseEffect pour les effets
Pas de hooks (useState, etc.)Hooks complets disponibles

Résumé

  • Un Server Component ne peut ni utiliser de hooks, ni gérer d'événements : il rend du HTML/RSC payload, un point c'est tout.
  • "use client" marque la frontière : tout ce qui suit (et ses imports) rejoint le bundle JavaScript client.
  • Un Server Component peut englober un Client Component, jamais l'inverse directement (sauf via children).
  • Les données sensibles (clés API, requêtes DB) restent confinées aux Server Components, jamais exposées au client.

Exercices pratiques

1 disponible
1

Mission : rapatrier une clé secrète hors du bundle client

Objectif : Corriger une fuite de secret côté client causée par un mauvais placement de "use client", et concevoir la frontière serveur/client correcte.

Contexte

Un audit de sécurité repère, dans le bundle JavaScript envoyé au navigateur, la chaîne littérale d'une clé d'API de paiement. En creusant src/app/cours/[id]/page.jsx, le fichier commence par "use client"; tout en haut, puis importe STRIPE_SECRET_KEY depuis un fichier de config et fait await db.cours.findUnique(...) directement dans le composant, à côté d'un bouton "Ajouter aux favoris" qui a besoin de useState pour son état visuel.

Résoudre l’exercice →