Retour au blog

React Server Components : Le Guide Définitif pour Comprendre le Plus Grand Changement de React en 2025

Salut HaWkers, les React Server Components (RSC) représentent l'évolution la plus significative de React depuis les hooks. En 2025, des frameworks comme Next.js 14+ ont rendu les RSC mainstream, changeant complètement notre façon de penser le rendu et l'architecture des applications.

Vous êtes-vous déjà demandé pourquoi vos applications React chargent autant de JavaScript côté client ? Et si vous pouviez exécuter des composants directement sur le serveur, sans envoyer de code au navigateur ?

Qu'est-ce que les React Server Components ?

Les React Server Components sont des composants qui s'exécutent exclusivement sur le serveur. Contrairement au Server-Side Rendering (SSR) traditionnel, les RSC ne sont pas hydratés côté client - ils rendent simplement du HTML et l'envoient au navigateur, avec des instructions sur où les composants interactifs (Client Components) doivent être insérés.

Différence fondamentale :

  • SSR : Rendu sur le serveur, envoie du HTML, mais envoie aussi du JavaScript pour "hydrater" le composant côté client
  • RSC : Rendu sur le serveur, envoie seulement le résultat (HTML sérialisé), zéro JavaScript côté client pour ce composant

Cela signifie des bundles JavaScript dramatiquement plus petits et une performance bien supérieure, surtout sur les appareils plus lents.

Architecture : Server vs Client Components

La nouvelle architecture React divise les composants en deux catégories :

Server Components (par défaut)

// app/ProductList.jsx (Server Component par défaut)
import { db } from '@/lib/database';

export default async function ProductList() {
  // Accès direct à la base de données - sans API intermédiaire !
  const products = await db.query('SELECT * FROM products WHERE active = true');

  return (
    <div className="product-grid">
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

Caractéristiques :

  • Peuvent être async
  • Accès direct à la base de données, filesystem, APIs privées
  • Zéro JavaScript envoyé au client
  • Ne peuvent pas utiliser de hooks (useState, useEffect, etc)
  • Ne peuvent pas avoir d'event handlers

Client Components

'use client'; // Directive obligatoire

import { useState } from 'react';

export default function AddToCartButton({ productId }) {
  const [loading, setLoading] = useState(false);

  const handleClick = async () => {
    setLoading(true);
    await fetch(`/api/cart/add`, {
      method: 'POST',
      body: JSON.stringify({ productId })
    });
    setLoading(false);
  };

  return (
    <button onClick={handleClick} disabled={loading}>
      {loading ? 'Ajout...' : 'Ajouter au panier'}
    </button>
  );
}

Caractéristiques :

  • Nécessitent la directive 'use client'
  • Peuvent utiliser hooks et state
  • Peuvent avoir de l'interactivité
  • JavaScript envoyé au client
  • Fonctionnent comme les composants React traditionnels

Composition : Mélanger Server et Client Components

Le vrai pouvoir vient de la composition. Les Server Components peuvent rendre des Client Components, et les Client Components peuvent recevoir des Server Components comme children :

// app/ProductPage.jsx (Server Component)
import ProductDetails from './ProductDetails'; // Server
import AddToCartButton from './AddToCartButton'; // Client
import Reviews from './Reviews'; // Server

export default async function ProductPage({ params }) {
  const product = await db.products.findUnique({
    where: { id: params.id },
    include: { reviews: true }
  });

  return (
    <div className="product-page">
      {/* Server Component - sans JS côté client */}
      <ProductDetails product={product} />

      {/* Client Component - avec interactivité */}
      <AddToCartButton productId={product.id} />

      {/* Server Component - liste statique */}
      <Reviews reviews={product.reviews} />
    </div>
  );
}

React Server Components architecture

Data Fetching : La Nouvelle Ère

Avec les RSC, le data fetching devient dramatiquement plus simple. Plus besoin de getServerSideProps, getStaticProps, ou de gestionnaires d'état complexes :

// app/dashboard/page.jsx
async function getData() {
  // Fetch direct, peut être mis en cache
  const [user, stats, notifications] = await Promise.all([
    db.user.findUnique({ where: { id: userId } }),
    db.analytics.aggregate({ userId }),
    db.notifications.findMany({ userId, unread: true })
  ]);

  return { user, stats, notifications };
}

export default async function Dashboard() {
  const { user, stats, notifications } = await getData();

  return (
    <div className="dashboard">
      <UserProfile user={user} />
      <StatsWidget stats={stats} />
      <NotificationBell count={notifications.length}>
        {/* Server Component passé comme children */}
        <NotificationList notifications={notifications} />
      </NotificationBell>
    </div>
  );
}

Avantages :

  • Colocation : fetch proche de là où c'est utilisé
  • Parallel fetching automatique
  • Pas de waterfalls
  • Cache natif de React
  • Pas de loading states complexes

Streaming et Suspense

Les RSC s'intègrent parfaitement avec Suspense pour le streaming progressif :

import { Suspense } from 'react';

export default function Page() {
  return (
    <div>
      {/* Contenu statique rendu immédiatement */}
      <Header />

      {/* Suspense permet le streaming des parties lentes */}
      <Suspense fallback={<ProductListSkeleton />}>
        <ProductList />
      </Suspense>

      <Suspense fallback={<ReviewsSkeleton />}>
        <Reviews />
      </Suspense>

      <Footer />
    </div>
  );
}

async function ProductList() {
  // Query lente (3 secondes)
  const products = await slowDatabaseQuery();
  return <div>{/* render products */}</div>;
}

async function Reviews() {
  // API externe lente (2 secondes)
  const reviews = await fetch('https://api.reviews.com/...');
  return <div>{/* render reviews */}</div>;
}

Le navigateur reçoit :

  1. Immédiatement : Header + skeletons
  2. Après 2s : Reviews (via streaming)
  3. Après 3s : ProductList (via streaming)

L'utilisateur voit le contenu progressivement, sans attendre que tout soit chargé.

Optimisation de Performance

Les RSC apportent des gains de performance significatifs :

Réduction du Bundle

Exemple réel de migration Next.js 13 → Next.js 14 avec RSC :

Avant (Client Components) :

  • Bundle JS : 850KB
  • First Contentful Paint : 2.1s
  • Time to Interactive : 3.8s

Après (Server Components) :

  • Bundle JS : 120KB (-85%)
  • First Contentful Paint : 0.8s (-62%)
  • Time to Interactive : 1.2s (-68%)

Élimination des Dépendances Lourdes

// Server Component - zéro impact sur le bundle
import { marked } from 'marked'; // 50KB
import hljs from 'highlight.js'; // 150KB
import { formatDate } from 'date-fns'; // 25KB

export default async function BlogPost({ slug }) {
  const post = await getPost(slug);
  const html = marked(post.content);
  const highlighted = hljs.highlightAuto(html);

  return (
    <article>
      <time>{formatDate(post.date, 'PPP')}</time>
      <div dangerouslySetInnerHTML={{ __html: highlighted.value }} />
    </article>
  );
}

// Ces 225KB de dépendances NE vont PAS vers le client !

Patterns et Best Practices

1. Déplacer l'Interactivité vers les Feuilles

// ❌ Mauvais : Client Component en haut
'use client';

export default function ProductPage({ product }) {
  return (
    <div>
      <ProductImage src={product.image} /> {/* N'a pas besoin d'être client */}
      <ProductDetails data={product} /> {/* N'a pas besoin d'être client */}
      <AddToCartButton productId={product.id} /> {/* Doit être client */}
    </div>
  );
}

// ✅ Bon : Client Component seulement où nécessaire
export default function ProductPage({ product }) {
  return (
    <div>
      <ProductImage src={product.image} /> {/* Server */}
      <ProductDetails data={product} /> {/* Server */}
      <AddToCartButton productId={product.id} /> {/* Client */}
    </div>
  );
}

2. Passer des Server Components comme Props

// Client Component
'use client';

export default function Tabs({ children }) {
  const [tab, setTab] = useState(0);

  return (
    <div>
      <TabButtons active={tab} onChange={setTab} />
      <div>{children[tab]}</div>
    </div>
  );
}

// Server Component
export default function Page() {
  return (
    <Tabs>
      {/* Chaque tab est un Server Component */}
      <ProductList />
      <ReviewsList />
      <SpecsList />
    </Tabs>
  );
}

3. Composition Stratégique

Les Server Components doivent faire le travail lourd (data fetching, computation), tandis que les Client Components gèrent uniquement l'interactivité.

Défis et Limitations

Les RSC ne sont pas une solution miracle. Il existe des défis :

1. Courbe d'Apprentissage : Un paradigme différent nécessite de repenser l'architecture.

2. Debugging Complexe : Les erreurs peuvent survenir sur le serveur ou le client, compliquant le troubleshooting.

3. Limitations du Context : L'API Context ne fonctionne pas entre les frontières server et client.

4. Sérialisation : Les props passées de Server → Client doivent être sérialisables (pas de fonctions, classes, etc).

5. Caching Complexe : Le système de cache de React est puissant mais peut être confus.

Le Futur de React

Les React Server Components sont le futur. En 2025 :

  • Next.js App Router a rendu les RSC par défaut
  • Remix adopte une architecture similaire
  • D'autres frameworks React suivront

Bénéfices attendus :

  • Applications plus rapides par défaut
  • Bundles plus petits automatiquement
  • Meilleur SEO natif
  • Architecture plus simple

Si vous vous sentez inspiré par les React Server Components, je recommande un autre article : Server-First Development : Comment SvelteKit, Astro et Remix Redéfinissent le Développement Web où vous découvrirez d'autres approches server-first.

C'est parti !

Commentaires (0)

Cet article n'a pas encore de commentaires. Soyez le premier!

Ajouter des commentaires