Retour au blog

React Server Components : Guide Complet et Pratique Pour 2025

Salut HaWkers, les React Server Components (RSC) ont quitté le statut de feature expérimentale pour devenir le standard de facto du développement React moderne. Avec Next.js 15 consolidant le modèle et d'autres frameworks adoptant la technologie, comprendre les RSC en profondeur est essentiel pour tout développeur frontend en 2025.

Dans ce guide, nous allons explorer non seulement le "comment", mais principalement le "quand" et le "pourquoi" utiliser les Server Components.

Qu'est-Ce Que les Server Components

Les React Server Components sont des composants qui s'exécutent exclusivement sur le serveur, n'étant jamais envoyés au navigateur de l'utilisateur. Cela représente un changement fondamental dans le modèle mental de construction d'applications React.

Modèle Mental Traditionnel vs RSC

React Traditionnel (Client-Side Rendering) :

  1. L'utilisateur accède à la page
  2. Le serveur envoie un HTML minimal + JavaScript
  3. Le JavaScript se télécharge, parse et s'exécute
  4. React "hydrate" la page, la rendant interactive
  5. Les données sont récupérées via API (useEffect, React Query, etc.)
  6. Les composants se re-rendent avec les données

React Server Components :

  1. L'utilisateur accède à la page
  2. Le serveur exécute les composants et récupère les données
  3. Le serveur envoie le HTML rendu + JavaScript minimal
  4. Seuls les composants interactifs sont hydratés
  5. La page arrive déjà prête et fonctionnelle

La Grande Différence

// ❌ Composant Client Traditionnel
'use client'

import { useState, useEffect } from 'react';

export function ProductList() {
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Le fetch se fait côté CLIENT après le render initial
    fetch('/api/products')
      .then(res => res.json())
      .then(data => {
        setProducts(data);
        setLoading(false);
      });
  }, []);

  if (loading) return <ProductSkeleton />;

  return (
    <ul>
      {products.map(p => <ProductCard key={p.id} product={p} />)}
    </ul>
  );
}
// ✅ Server Component
// Pas besoin de 'use client' - c'est server par défaut

import { db } from '@/lib/database';

export async function ProductList() {
  // Le fetch se fait côté SERVEUR avant d'envoyer le HTML
  const products = await db.products.findMany({
    where: { active: true },
    orderBy: { createdAt: 'desc' }
  });

  return (
    <ul>
      {products.map(p => <ProductCard key={p.id} product={p} />)}
    </ul>
  );
}

Bénéfices Concrets

Quantifions les bénéfices des Server Components avec des exemples réels.

Réduction de la Taille du Bundle

// Analyse de bundle typique

// Application e-commerce avec React traditionnel
const clientBundleTraditional = {
  react: '42kb',
  reactDom: '130kb',
  reactQuery: '35kb',
  axios: '14kb',
  zustand: '8kb',
  dateFormats: '72kb', // date-fns, moment, etc.
  markdown: '45kb',    // pour rendre les descriptions
  syntax: '180kb',     // highlight.js pour les blocs de code
  total: '526kb gzipped'
};

// Même application avec RSC
const clientBundleRSC = {
  react: '42kb',
  reactDom: '130kb',
  // reactQuery: pas besoin pour le data fetching
  // axios: pas besoin, fetch côté serveur
  zustand: '8kb',      // encore nécessaire pour l'état client
  // dateFormats: rendu côté serveur
  // markdown: rendu côté serveur
  // syntax: rendu côté serveur
  total: '180kb gzipped' // 66% plus petit !
};

Performance de Chargement

// Métriques réelles d'une application migrée vers RSC

// Avant (CSR)
const metricsCSR = {
  TTFB: '180ms',
  FCP: '1.8s',
  LCP: '3.2s',
  TTI: '4.1s',
  CLS: 0.12,
  bundleSize: '450kb'
};

// Après (RSC)
const metricsRSC = {
  TTFB: '220ms',      // Légèrement plus élevé (rendu serveur)
  FCP: '0.9s',        // 50% plus rapide
  LCP: '1.4s',        // 56% plus rapide
  TTI: '1.8s',        // 56% plus rapide
  CLS: 0.02,          // 83% meilleur (moins de layout shift)
  bundleSize: '180kb' // 60% plus petit
};

Quand Utiliser Server vs Client Components

La décision entre Server et Client Components doit être basée sur des critères clairs.

Utilisez les Server Components Quand

// ✅ Récupération de données
async function UserProfile({ userId }: { userId: string }) {
  const user = await db.users.findUnique({ where: { id: userId } });
  return <ProfileCard user={user} />;
}

// ✅ Accès aux ressources du serveur
async function ConfigPanel() {
  const config = await readFile('./config.json', 'utf-8');
  return <ConfigDisplay config={JSON.parse(config)} />;
}

// ✅ Rendu de contenu lourd
import { marked } from 'marked';
import hljs from 'highlight.js';

async function BlogPost({ slug }: { slug: string }) {
  const post = await getPost(slug);
  const html = marked(post.content, {
    highlight: (code, lang) => hljs.highlight(code, { language: lang }).value
  });

  return <article dangerouslySetInnerHTML={{ __html: html }} />;
}

// ✅ Données sensibles
async function AdminDashboard() {
  // Les secrets ne vont jamais au client
  const analytics = await fetchAnalytics(process.env.ANALYTICS_SECRET);
  return <DashboardCharts data={analytics} />;
}

Utilisez les Client Components Quand

'use client'

// ✅ Interactivité avec état
import { useState } from 'react';

export function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(c => c + 1)}>
      Clics : {count}
    </button>
  );
}

// ✅ Effets et lifecycle
import { useEffect } from 'react';

export function AnalyticsTracker() {
  useEffect(() => {
    trackPageView();
  }, []);

  return null;
}

// ✅ Event handlers
export function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
  return (
    <input
      type="search"
      onChange={(e) => onSearch(e.target.value)}
      placeholder="Rechercher..."
    />
  );
}

// ✅ APIs du navigateur
export function LocationDisplay() {
  const [coords, setCoords] = useState<GeolocationCoordinates | null>(null);

  useEffect(() => {
    navigator.geolocation.getCurrentPosition(
      (pos) => setCoords(pos.coords)
    );
  }, []);

  return coords ? (
    <span>Lat: {coords.latitude}, Lng: {coords.longitude}</span>
  ) : null;
}

// ✅ Hooks personnalisés avec état
import { useLocalStorage } from '@/hooks/useLocalStorage';

export function ThemeToggle() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');
  return (
    <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
      Thème : {theme}
    </button>
  );
}

Patterns de Composition

L'art des RSC réside dans la composition efficace de Server et Client Components.

Le Pattern "Wrapper"

// Server Component qui enveloppe un Client Component
// page.tsx (Server Component)

import { ProductFilters } from './ProductFilters'; // Client
import { db } from '@/lib/database';

export default async function ProductsPage() {
  // Données récupérées côté serveur
  const categories = await db.categories.findMany();
  const brands = await db.brands.findMany();

  return (
    <div>
      <h1>Produits</h1>
      {/* Le Client Component reçoit les données du serveur en props */}
      <ProductFilters
        categories={categories}
        brands={brands}
      />
      {/* Le reste de la page est Server Component */}
      <ProductGrid />
    </div>
  );
}

Le Pattern "Slot"

// Server Component qui accepte des Client Components comme children

// Modal.tsx (Server Component)
interface ModalProps {
  title: string;
  children: React.ReactNode; // Peut être Client ou Server
}

export function Modal({ title, children }: ModalProps) {
  return (
    <div className="modal-backdrop">
      <div className="modal-content">
        <h2>{title}</h2>
        {children}
      </div>
    </div>
  );
}

// Usage dans page.tsx
import { Modal } from './Modal';
import { InteractiveForm } from './InteractiveForm'; // Client Component

export default function Page() {
  return (
    <Modal title="Nouveau Produit">
      <InteractiveForm /> {/* Client Component à l'intérieur de Server */}
    </Modal>
  );
}

Le Pattern "Island"

// page.tsx - Majoritairement Server avec des "îlots" d'interactivité

import { Suspense } from 'react';
import { LikeButton } from './LikeButton';        // Client
import { CommentSection } from './CommentSection'; // Client
import { ShareMenu } from './ShareMenu';           // Client

export default async function BlogPost({ slug }: { slug: string }) {
  const post = await getPost(slug);
  const author = await getAuthor(post.authorId);

  return (
    <article>
      {/* Contenu statique - Server */}
      <header>
        <h1>{post.title}</h1>
        <AuthorCard author={author} />
        <time>{formatDate(post.publishedAt)}</time>
      </header>

      {/* Contenu rendu - Server */}
      <div dangerouslySetInnerHTML={{ __html: post.htmlContent }} />

      {/* Îlots d'interactivité - Client */}
      <footer>
        <LikeButton postId={post.id} initialLikes={post.likes} />
        <ShareMenu url={`/blog/${slug}`} title={post.title} />
      </footer>

      {/* Section interactive avec état de chargement */}
      <Suspense fallback={<CommentSkeleton />}>
        <CommentSection postId={post.id} />
      </Suspense>
    </article>
  );
}

Streaming et Suspense

Les RSC permettent le streaming de HTML, améliorant significativement l'expérience perçue.

Streaming Basique

// page.tsx
import { Suspense } from 'react';

export default function Dashboard() {
  return (
    <div className="dashboard">
      <h1>Dashboard</h1>

      {/* Rendu immédiat */}
      <QuickStats />

      {/* Streamé quand prêt */}
      <Suspense fallback={<ChartSkeleton />}>
        <RevenueChart /> {/* Async Server Component */}
      </Suspense>

      <Suspense fallback={<TableSkeleton />}>
        <RecentOrders /> {/* Async Server Component */}
      </Suspense>

      <Suspense fallback={<ListSkeleton />}>
        <TopProducts /> {/* Async Server Component */}
      </Suspense>
    </div>
  );
}

// Chaque composant async récupère les données indépendamment
async function RevenueChart() {
  const data = await fetchRevenueData(); // Peut prendre 2s
  return <Chart data={data} />;
}

async function RecentOrders() {
  const orders = await fetchRecentOrders(); // Peut prendre 1s
  return <OrdersTable orders={orders} />;
}

async function TopProducts() {
  const products = await fetchTopProducts(); // Peut prendre 500ms
  return <ProductsList products={products} />;
}

Parallel Data Fetching

// ✅ Correct : Fetches en parallèle
async function Dashboard() {
  // Lance tous les fetches simultanément
  const revenuePromise = fetchRevenue();
  const ordersPromise = fetchOrders();
  const productsPromise = fetchProducts();

  // Attend tous
  const [revenue, orders, products] = await Promise.all([
    revenuePromise,
    ordersPromise,
    productsPromise
  ]);

  return (
    <div>
      <RevenueChart data={revenue} />
      <OrdersTable data={orders} />
      <ProductsList data={products} />
    </div>
  );
}

// ❌ Mauvais : Fetches séquentiels (waterfall)
async function DashboardSlow() {
  const revenue = await fetchRevenue();    // Attend 1s
  const orders = await fetchOrders();      // Puis attend encore 1s
  const products = await fetchProducts();  // Puis encore 500ms
  // Total : 2.5s

  return (/* ... */);
}

// Avec Promise.all : 1s (le plus lent)
// Sans Promise.all : 2.5s (somme de tous)

Server Actions

Les Server Actions complètent les RSC en permettant des mutations de façon élégante.

Server Actions Basiques

// actions.ts
'use server'

import { revalidatePath } from 'next/cache';
import { db } from '@/lib/database';

export async function createProduct(formData: FormData) {
  const name = formData.get('name') as string;
  const price = parseFloat(formData.get('price') as string);

  await db.products.create({
    data: { name, price }
  });

  revalidatePath('/products');
}

export async function deleteProduct(productId: string) {
  await db.products.delete({
    where: { id: productId }
  });

  revalidatePath('/products');
}
// ProductForm.tsx - Peut être un Server Component !
import { createProduct } from './actions';

export function ProductForm() {
  return (
    <form action={createProduct}>
      <input name="name" placeholder="Nom du produit" required />
      <input name="price" type="number" step="0.01" required />
      <button type="submit">Créer Produit</button>
    </form>
  );
}

Erreurs Courantes et Comment les Éviter

Apprenez des erreurs les plus fréquentes en travaillant avec les RSC.

Erreur 1 : Importer un Client Component sans 'use client'

// ❌ Erreur courante
// Button.tsx - Oubli de 'use client'
import { useState } from 'react';

export function Button() {
  const [clicked, setClicked] = useState(false);
  // Erreur : useState ne fonctionne pas dans les Server Components
}

// ✅ Correct
// Button.tsx
'use client'

import { useState } from 'react';

export function Button() {
  const [clicked, setClicked] = useState(false);
  // Fonctionne !
}

Erreur 2 : Passer des Fonctions en Props aux Client Components

// ❌ Erreur
// page.tsx (Server Component)
export default function Page() {
  function handleClick() {
    console.log('clicked');
  }

  // Erreur : les fonctions ne sont pas sérialisables
  return <ClientButton onClick={handleClick} />;
}

// ✅ Correct - Utilisez des Server Actions
// page.tsx
import { handleAction } from './actions';

export default function Page() {
  return <ClientButton action={handleAction} />;
}

// actions.ts
'use server'
export async function handleAction() {
  console.log('action executed');
}

Conclusion

Les React Server Components représentent la plus grande évolution dans l'architecture React depuis les Hooks. Ils résolvent des problèmes réels de performance et d'expérience utilisateur que les développeurs affrontent depuis des années.

Le secret pour maîtriser les RSC est de comprendre clairement la division des responsabilités : Server Components pour les données et le rendu lourd, Client Components pour l'interactivité. Avec cette mentalité, vous construirez des applications plus rapides, plus simples et plus scalables.

Si vous voulez approfondir vos connaissances en React moderne, je recommande de consulter un autre article : React Hooks Avancés : Patterns et Optimisation où vous découvrirez des techniques qui complètent l'utilisation des Server Components.

C'est parti ! 🦅

Commentaires (0)

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

Ajouter des commentaires