React Server Components et Next.js App Router : Le Guide Définitif


Les React Server Components (RSC) ont redéfini la façon dont on construit des applications React. Fini le paradigme “tout côté client avec des useEffect partout”. Avec Next.js 15 et l’App Router, le serveur redevient un citoyen de premier plan — et c’est une excellente nouvelle.

Ce guide couvre tout ce qu’un développeur senior doit savoir pour maîtriser les RSC en production.

Le modèle mental des Server Components

Avant les RSC, React avait un modèle simple : tout le code tourne dans le navigateur. Les données arrivent via des appels API, l’UI se construit côté client.

Avec les RSC, React fonctionne sur deux runtimes :

  • Serveur : les Server Components s’exécutent côté serveur, accèdent directement aux bases de données, fichiers, APIs, et envoient du HTML sérialisé au client. Zéro JavaScript envoyé au navigateur pour ces composants.
  • Client : les Client Components ('use client') fonctionnent comme avant — état, effets, event handlers, APIs navigateur.

La clé : par défaut, tout composant dans l’App Router est un Server Component. Le client est l’exception, pas la règle.

Cette séparation s’inscrit dans une réflexion plus large sur l’architecture des applications web modernes, où l’isolation des responsabilités est fondamentale.

Server vs Client Components

Quand utiliser 'use client'

Ajoutez 'use client' uniquement quand le composant a besoin de :

  • État : useState, useReducer
  • Effets : useEffect, useLayoutEffect
  • Event handlers : onClick, onChange, onSubmit
  • APIs navigateur : localStorage, window, IntersectionObserver
  • Hooks custom qui utilisent les éléments ci-dessus

Tout le reste reste Server Component par défaut.

Patterns de composition

Le pattern le plus puissant : passer des Server Components comme children de Client Components.

// Layout.tsx (Server Component)
import { Sidebar } from './Sidebar' // Client Component
import { Navigation } from './Navigation' // Server Component

export default function Layout() {
  return (
    <Sidebar>
      <Navigation /> {/* Server Component passé comme children */}
    </Sidebar>
  )
}
// Sidebar.tsx
'use client'
export function Sidebar({ children }: { children: React.ReactNode }) {
  const [isOpen, setIsOpen] = useState(true)
  return (
    <aside className={isOpen ? 'w-64' : 'w-0'}>
      {children} {/* Rendu côté serveur, injecté ici */}
    </aside>
  )
}

Navigation est rendu côté serveur — zéro JS envoyé — mais s’affiche à l’intérieur de Sidebar qui gère l’état d’ouverture. Le meilleur des deux mondes.

Data fetching avec les Server Components

Fetch directement dans le composant

// app/products/page.tsx
export default async function ProductsPage() {
  const products = await db.product.findMany({
    orderBy: { createdAt: 'desc' },
    take: 20,
  })

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

Pas de useEffect. Pas de state loading. Pas d’API route intermédiaire. Le composant fetch ses données directement — sur le serveur — et envoie le HTML final.

Server Actions pour les mutations

// app/products/actions.ts
'use server'

export async function createProduct(formData: FormData) {
  const product = await db.product.create({
    data: {
      name: formData.get('name') as string,
      price: Number(formData.get('price')),
    },
  })
  revalidatePath('/products')
  redirect(`/products/${product.id}`)
}

Les Server Actions remplacent les API routes pour les mutations simples. Elles sont type-safe, progressively enhanced (fonctionnent sans JS), et gèrent la revalidation du cache.

Streaming et Suspense

Le streaming est l’arme secrète des RSC pour la performance perçue. Au lieu d’attendre que toute la page soit prête, Next.js envoie le HTML progressivement.

import { Suspense } from 'react'

export default function DashboardPage() {
  return (
    <div>
      <h1>Dashboard</h1>
      <Suspense fallback={<StatsSkeleton />}>
        <Stats /> {/* Peut prendre 2s à charger */}
      </Suspense>
      <Suspense fallback={<ChartSkeleton />}>
        <RevenueChart /> {/* Peut prendre 3s à charger */}
      </Suspense>
    </div>
  )
}

Le <h1> s’affiche immédiatement. Stats apparaît dès que ses données sont prêtes. RevenueChart arrive ensuite. L’utilisateur voit du contenu en millisecondes au lieu d’attendre 3 secondes devant un spinner.

Performance : l’impact concret des RSC

Les RSC réduisent drastiquement le JavaScript envoyé au client :

  • Avant (SPA classique) : tout le code de data fetching, les dépendances de parsing (date-fns, zod, etc.), et le rendering initial sont dans le bundle client.
  • Après (RSC) : seuls les Client Components et leurs dépendances sont envoyés. Les Server Components restent côté serveur.

Sur un projet e-commerce récent, la migration vers RSC a réduit le bundle JavaScript de 40% et amélioré le LCP de 1.2 secondes. Pour aller plus loin sur les métriques de performance, consultez mon guide sur la performance web et les Core Web Vitals.

La comparaison avec l’écosystème Vue est intéressante : la Composition API de Vue.js offre une approche différente mais complémentaire du même problème de composition et de réutilisabilité.

Pièges courants et solutions

1. Le “use client” viral. Un 'use client' en haut d’un fichier rend tout le sous-arbre client. Solution : isolez les parties interactives dans des composants dédiés.

2. Sérialisation des props. Les props passées de Server à Client Components doivent être sérialisables (pas de fonctions, pas de classes). Solution : utilisez des Server Actions pour les callbacks.

3. Cache et revalidation. Le cache par défaut de Next.js peut servir des données stales. Solution : configurez revalidate explicitement et utilisez revalidatePath/revalidateTag.

4. Hydration mismatch. Le HTML serveur ne correspond pas au rendu client. Solution : évitez les valeurs aléatoires ou basées sur Date.now() dans les Server Components sans les wrapper dans Suspense.

En résumé

Les React Server Components avec Next.js 15, c’est :

  • Server by default : moins de JS, plus de performance
  • Data fetching simplifié : async/await directement dans les composants
  • Streaming natif : HTML progressif via Suspense
  • Server Actions : mutations sans API routes

Le modèle mental demande un temps d’adaptation, mais le résultat est net : des applications plus rapides, plus simples, et plus maintenables.

KD

Kevin De Vaubree

Développeur Full-Stack Senior