Dix-huit mois. C’est le temps qu’il aura fallu pour que les React Server Components passent du concept fascinant mais théorique à une réalité de production que l’on déploie sans trembler. Fin 2024, React 19 stabilisait les RSC ; depuis, j’ai migré trois projets complets sur cette architecture, du petit site vitrine au dashboard tentaculaire. Le bilan n’est ni un dithyrambe ni un réquisitoire : c’est un retour de terrain, mesures à l’appui. Voici ce qui tient ses promesses, ce qui résiste encore, et comment décider si les RSC ont leur place dans votre prochain projet.

Le modèle RSC : deux mondes, une seule arborescence

La rupture conceptuelle des React Server Components tient en une phrase : un composant peut désormais s’exécuter exclusivement sur le serveur, sans jamais envoyer son code au navigateur. Là où un composant React traditionnel était forcément hydraté côté client, un Server Component produit son HTML sur le serveur et l’expédie tel quel. Il ne fait pas partie du bundle JavaScript livré à l’utilisateur. Cette distinction redéfinit la frontière entre ce qui appartient au serveur et ce qui doit vivre dans le navigateur, et c’est tout l’enjeu de l’apprentissage.

Dans le modèle RSC, les Server Components forment le squelette par défaut de l’arborescence. Ils orchestrent les données, composent la page, et délèguent l’interactivité à des îlots de Client Components clairement identifiés. Un Server Component peut importer et rendre un Client Component, mais l’inverse est plus subtil : un Client Component ne peut recevoir un Server Component que via la prop children ou une autre prop, jamais en l’important directement. Cette règle de composition, déroutante au premier abord, devient vite un réflexe et structure proprement le code.

La directive « use client » : tracer la frontière

Concrètement, comment React sait-il qu’un composant doit être hydraté côté client ? Par la directive « use client », placée en première ligne d’un fichier. Elle marque le point de bascule : à partir de ce module et pour tout ce qu’il importe, on entre dans le territoire client. Tout ce qui réclame de l’état, des effets ou des gestionnaires d’événements doit vivre derrière cette frontière. Sans « use client », un composant est considéré comme Server Component, ne peut pas utiliser useState ni useEffect, mais gagne le droit d’être async et de lire directement vos sources de données.

L’erreur classique du débutant consiste à saupoudrer « use client » en haut de chaque fichier par précaution, par mimétisme avec l’ancien monde. C’est exactement ce qu’il faut éviter : chaque « use client » élargit la part du bundle expédiée au navigateur et annule une partie du bénéfice des RSC. La bonne discipline consiste à descendre la directive le plus bas possible dans l’arbre, à l’isoler sur les seuls composants réellement interactifs — un bouton, un champ de saisie, un menu déroulant — et à laisser tout le reste sur le serveur.

Rendu serveur et streaming avec Suspense

Les RSC ne se contentent pas de générer du HTML : ils s’appuient sur le streaming pour livrer la page par morceaux. Plutôt que d’attendre que la totalité de l’arborescence soit prête avant d’envoyer quoi que ce soit, le serveur émet d’abord le contenu disponible immédiatement, puis pousse les fragments restants au fur et à mesure de leur résolution. C’est le mariage des Server Components et de Suspense qui rend cela possible : on enveloppe une portion lente dans une balise Suspense, on déclare un fallback, et React s’occupe d’orchestrer l’arrivée progressive du contenu.

Le gain perçu est considérable. L’utilisateur voit la structure de la page et le contenu statique quasi instantanément, pendant que les zones dépendant de requêtes plus lentes affichent un état d’attente localisé puis se remplissent. On ne bloque plus tout l’écran derrière la requête la plus lente. Ce mécanisme remplace élégamment l’ancien ballet de spinners pilotés à la main : au lieu de gérer manuellement des états de chargement avec useState, on décrit les frontières de chargement de façon déclarative, et le streaming fait le reste sans une ligne de logique impérative.

Le data fetching dans les Server Components

C’est ici que le modèle révèle toute son élégance. Un Server Component peut être une fonction async ; il peut donc utiliser await directement dans son corps pour récupérer ses données, qu’il s’agisse d’un appel à la base de données, d’une lecture de fichier ou d’un fetch HTTP. Fini le triptyque useEffect + useState + état de chargement qui peuplait chaque composant chargé de données. Le composant lit ce dont il a besoin, puis retourne son JSX. Comme le code ne quitte jamais le serveur, on peut y manipuler des secrets, des identifiants de connexion ou des requêtes SQL sans le moindre risque d’exposition au navigateur.

Voici le pattern de référence : un Server Component asynchrone qui interroge la base, et un Client Component dédié à l’interactivité, importé par le premier. La séparation est nette, le flux de données descend du serveur vers les îlots interactifs.

// article-list.tsx — Server Component (aucune directive)
import { db } from '@/lib/db';
import { LikeButton } from './like-button';

export default async function ArticleList() {
  // await directement dans le composant : pas de useEffect
  const articles = await db.article.findMany({ take: 10 });

  return (
    <ul>
      {articles.map((a) => (
        <li key={a.id}>
          {a.title}
          {/* îlot interactif : Client Component */}
          <LikeButton articleId={a.id} initial={a.likes} />
        </li>
      ))}
    </ul>
  );
}

// like-button.tsx — Client Component
'use client';
import { useState } from 'react';

export function LikeButton({ articleId, initial }: { articleId: string; initial: number }) {
  const [likes, setLikes] = useState(initial);
  return (
    <button onClick={() => setLikes((n) => n + 1)}>
      ❤ {likes}
    </button>
  );
}

Ce découpage illustre la philosophie : la donnée est résolue côté serveur sans coût client, et seul le bouton — quelques octets de logique — voyage jusqu’au navigateur pour devenir interactif.

La réduction du bundle JavaScript : des chiffres, pas du marketing

Le bénéfice le plus tangible des RSC est la fonte du JavaScript expédié au client. Sur mon projet le plus lourd, un dashboard de plus de cinquante composants, le JS client est passé de 240 Ko à 18 Ko. Ce n’est pas une estimation optimiste : c’est une mesure relevée sur le bundle de production. La raison est mécanique — tout le code des Server Components, ainsi que les dépendances qu’ils mobilisent pour le rendu, restent sur le serveur et ne sont jamais téléchargés ni hydratés par le navigateur.

Cette diminution se traduit directement dans les métriques de performance perçue. Sur ce même dashboard, le First Contentful Paint est tombé de 2,1 secondes à 0,6 seconde. Moins de JavaScript à parser et à exécuter, c’est un thread principal libéré plus tôt et une interactivité atteinte plus vite. Pour les applications riches en contenu, où chaque kilo-octet de bundle pèse sur le temps de démarrage, l’architecture RSC déplace structurellement le curseur du côté de la performance, sans bricolage de code-splitting manuel.

Next.js App Router : l’implémentation de référence

Les RSC restent une primitive de bas niveau ; en pratique, c’est un framework qui les rend exploitables. L’App Router de Next.js est aujourd’hui l’implémentation de référence : il traite tout composant comme un Server Component par défaut, gère le bundler côté serveur, orchestre le streaming et expose un système de routage fondé sur ce modèle. C’est dans cet environnement que j’ai mené mes trois migrations, et c’est le terrain le plus mature pour quiconque veut adopter les Server Components sans réinventer toute la plomberie sous-jacente.

L’App Router apporte aussi son lot de concepts à digérer : les Server Actions pour les mutations, marquées par « use server », le système de cache et de revalidation propre à Next.js, et les conventions de fichiers pour les layouts et les frontières de chargement. Cette richesse est une force mais aussi une source de confusion, car il faut apprendre simultanément le modèle RSC de React et la couche d’abstraction que Next.js pose par-dessus. Distinguer ce qui relève de React et ce qui relève du framework est l’une des clés pour ne pas se perdre.

Les limites : ce qui résiste encore

Tout n’est pas réglé, loin de là. La première limite est structurelle : un Server Component ne peut pas utiliser les hooks d’état ni d’effet. Pas de useState, pas de useEffect, pas de useContext, pas de gestionnaire d’événements. Dès qu’un besoin d’interactivité apparaît, il faut basculer derrière une frontière « use client ». S’ajoute la contrainte de sérialisation : les props passées d’un Server Component à un Client Component doivent être sérialisables. On ne peut pas transmettre une fonction, une instance de classe ou un objet non sérialisable à travers cette frontière, ce qui oblige parfois à repenser la circulation des données.

L’écosystème, ensuite, reste en pleine adaptation. Toute librairie reposant sur useState, useEffect, createContext ou les événements DOM réclame un wrapper « use client » pour fonctionner dans une arborescence RSC. Beaucoup de bibliothèques populaires n’exposent pas encore de frontière client propre, et l’on se retrouve à écrire des composants tampons pour les intégrer. Le débogage, enfin, demeure inconfortable : les stack traces entremêlent serveur et client, et les React DevTools ne montrent pas les Server Components, ce qui complique l’inspection d’une arborescence mixte.

DX, pièges courants et recommandations

L’expérience de développement est ambivalente. Quand le modèle mental est en place, écrire un composant qui lit ses données et retourne du JSX est d’une simplicité désarmante : plus de spinner, plus d’état de chargement, plus d’effet pour aller chercher des données. Mais la courbe d’apprentissage est réelle. Comprendre « use client » face à « use server », distinguer les Server Actions du cache Next.js, savoir où poser la frontière : tout cela prend du temps, et les développeurs juniors sont visiblement perdus les premières semaines. Le piège le plus fréquent reste l’abus de « use client », qui annihile silencieusement le bénéfice de l’architecture.

Ma recommandation tient en trois cas de figure. Pour un nouveau projet Next.js, adoptez les RSC par défaut : le rapport bénéfice sur coût est clairement favorable, et vous construisez sur l’orientation que prend l’écosystème. Pour un projet existant, migrez progressivement, page par page, plutôt que d’entreprendre une réécriture massive et risquée. Pour une SPA classique, fortement interactive et sans enjeu fort de chargement initial, la complexité supplémentaire n’est pas justifiée : les Server Components y apporteraient plus de friction que de gains. Dix-huit mois après, le verdict est nuancé mais clair — les RSC sont une avancée solide, à condition de les adopter là où ils brillent vraiment.

Sources

W
WP Admin Lab

Architecte web full-stack. WordPress, performance, data et sécurité. Notes de terrain, tests reproductibles et retours d'expérience.