Retour au blog

React 19 Server Components en pratique

20 janvier 2026•Mis à jour le 5 septembre 2026•Dedimarco
react javascript web server-components
React 19 Server Components en pratique

En résumé : Au 5 septembre 2026, les React Server Components permettent de rendre des composants avant le bundle client, d’accéder aux données côté serveur et de réduire le JavaScript envoyé au navigateur. Adoptez-les progressivement dans un framework compatible, en conservant des Client Components pour l’état, les effets et les événements. Next.js n’est pas obligatoire, mais une intégration RSC adaptée l’est.

React 19 est stable depuis le 5 décembre 2024. Cette version réunit Server Components, Actions et APIs dédiées aux flux asynchrones. Le point clé n’est pas de tout déplacer côté serveur, mais de séparer clairement le rendu de données et l’interactivité.

Que sont les React Server Components ?

Les Server Components changent la façon dont React répartit le rendu. Contrairement aux composants classiques exécutés dans le navigateur, ils sont rendus avant le bundle client, au moment du build ou pour chaque requête. La documentation officielle des Server Components précise que leur propre code et leurs dépendances exclusivement serveur ne sont pas envoyés au navigateur.

Ce qu’il faut retenir

Un Server Component :

  • N’a pas de cycle de vie (pas de useEffect, useState, useContext)
  • N’a pas accès aux APIs du navigateur (DOM, window, localStorage)
  • Peut accéder directement à la base de données, au système de fichiers, ou aux variables d’environnement serveur
  • N’ajoute aucun poids JavaScript au bundle client

Les Server Components sont le comportement par défaut dans certains frameworks compatibles. Pour créer un Client Component, vous ajoutez la directive 'use client' en haut du fichier. Next.js n’est pas une obligation : React exige toutefois une intégration RSC fournie par un framework ou un bundler.

Architecture d’un arbre de composants

Un pattern typique combine les deux types :

// app/dashboard/page.tsx — Server Component (par défaut)
export default async function Dashboard() {
  // Cet appel s'exécute côté serveur, pas de requête fetch côté client
  const stats = await db.getDashboardStats();
  const recentOrders = await db.getRecentOrders();

  return (
    <main>
      <h1>Tableau de bord</h1>
      {/* Server Component : pas de JS envoyé au client */}
      <StatsGrid stats={stats} />

      {/* Client Component : interaction utilisateur nécessaire */}
      <OrderSearchForm />
      <OrderTable initialData={recentOrders} />
    </main>
  );
}
// components/OrderSearchForm.tsx
'use client'; // Directive requise pour les Client Components

import { useState } from 'react';

export function OrderSearchForm() {
  const [query, setQuery] = useState('');
  // ... logique interactive
}

Comment les Actions simplifient-elles les formulaires ?

React 19 introduit les Actions, une manière standardisée de gérer les opérations asynchrones déclenchées par les utilisateurs. Fini le boilerplate de useTransition + useState + gestion d’erreurs.

Fonctions serveur avec use server

Les fonctions asynchrones marquées par la directive 'use server' s’exécutent sur le serveur lorsqu’elles sont appelées depuis le client. La documentation React les nomme Server Functions ; une Server Function utilisée par une action de formulaire est une Server Action :

// app/actions/contact.ts
'use server';

import { z } from 'zod';

const contactSchema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
  message: z.string().min(10),
});

export async function submitContactAction(
  prevState: { error?: string; success?: boolean },
  formData: FormData,
) {
  const validated = contactSchema.safeParse({
    name: formData.get('name'),
    email: formData.get('email'),
    message: formData.get('message'),
  });

  if (!validated.success) {
    return { error: 'Données invalides' };
  }

  try {
    await db.contacts.create(validated.data);
    await sendNotificationEmail(validated.data);
    return { success: true };
  } catch (e) {
    return { error: "Erreur lors de l'envoi" };
  }
}

Formulaire avec useActionState

Le hook useActionState gère l’état du formulaire (pending, erreurs, retour) en un seul endroit :

// components/ContactForm.tsx
'use client';

import { useActionState } from 'react';
import { submitContactAction } from '@/app/actions/contact';

export function ContactForm() {
  const [state, action, isPending] = useActionState(submitContactAction, {
    error: undefined,
    success: undefined,
  });

  if (state.success) {
    return <p className="success">Message envoyé avec succès !</p>;
  }

  return (
    <form action={action}>
      <div>
        <label htmlFor="name">Nom</label>
        <input id="name" name="name" required disabled={isPending} />
      </div>
      <div>
        <label htmlFor="email">Email</label>
        <input id="email" name="email" type="email" required disabled={isPending} />
      </div>
      <div>
        <label htmlFor="message">Message</label>
        <textarea id="message" name="message" required disabled={isPending} />
      </div>

      {state.error && <p className="error">{state.error}</p>}

      <button type="submit" disabled={isPending}>
        {isPending ? 'Envoi...' : 'Envoyer'}
      </button>
    </form>
  );
}

useActionState remplace l’ancien useFormState. Il accepte une Action et un état initial, puis retourne l’état courant, l’Action à lier au formulaire, et le booléen isPending.

Le hook useFormStatus

Pour accéder à l’état de soumission depuis un composant enfant du formulaire, utilisez useFormStatus (depuis react-dom) :

'use client';

import { useFormStatus } from 'react-dom';

function SubmitButton() {
  const { pending } = useFormStatus();

  return (
    <button type="submit" disabled={pending}>
      {pending ? <span className="spinner" /> : 'Soumettre'}
    </button>
  );
}

Ce hook est scope au formulaire le plus proche, ce qui évite le prop drilling.

Comment use() lit-il des ressources pendant le rendu ?

L’API use() peut, contrairement aux hooks classiques, être appelée conditionnellement et dans une boucle. Elle lit notamment une Promise ou un Context pendant le rendu et s’intègre à Suspense.

Avec les Promises

// Server Component
async function UserProfile({ userId }: { userId: string }) {
  const userDataPromise = fetchUserData(userId);

  return <UserDetails promise={userDataPromise} />;
}

// Client Component
('use client');
import { use } from 'react';

function UserDetails({ promise }: { promise: Promise<UserData> }) {
  // Suspend le rendu jusqu'à ce que la promesse soit résolue
  const userData = use(promise);

  return (
    <div>
      <h2>{userData.name}</h2>
      <p>{userData.email}</p>
    </div>
  );
}

Avec le Context

'use client';
import { use, createContext } from 'react';

const ThemeContext = createContext('light');

function ThemedButton({ children }: { children: React.ReactNode }) {
  // use() remplace useContext, avec support conditionnel
  const theme = use(ThemeContext);

  return <button className={theme === 'dark' ? 'bg-gray-800' : 'bg-white'}>{children}</button>;
}

Comment useOptimistic donne-t-il un retour immédiat ?

Le hook useOptimistic permet une mise à jour optimiste de l’interface pendant une action. React affiche immédiatement la valeur provisoire, puis revient à la valeur reçue si l’opération échoue ou lorsque la valeur courante change.

'use client';

import { useOptimistic, useTransition } from 'react';

interface Comment {
  id: string;
  text: string;
  author: string;
}

function CommentSection({ initialComments }: { initialComments: Comment[] }) {
  const [optimisticComments, addOptimisticComment] = useOptimistic(
    initialComments,
    (state, newComment: Comment) => [...state, newComment],
  );

  const [isPending, startTransition] = useTransition();

  async function handleSubmit(formData: FormData) {
    const text = formData.get('text') as string;
    const tempComment: Comment = {
      id: crypto.randomUUID(),
      text,
      author: 'Vous',
    };

    startTransition(async () => {
      // Affiche immédiatement le commentaire
      addOptimisticComment(tempComment);

      // Appel serveur
      await addComment({ text });
    });
  }

  return (
    <div>
      <ul>
        {optimisticComments.map((comment) => (
          <li key={comment.id}>
            <strong>{comment.author}:</strong> {comment.text}
          </li>
        ))}
      </ul>

      <form action={handleSubmit}>
        <textarea name="text" required />
        <button type="submit" disabled={isPending}>
          Commenter
        </button>
      </form>
    </div>
  );
}

Quelle approche choisir pour les formulaires ?

Critère useState classique React Hook Form React 19 (Actions)
Boilerplate Élevé Modéré Minimal
Gestion pending/erreur Manuelle Intégrée Intégrée (useActionState)
Mises à jour optimistes Manuelles Non Oui (useOptimistic)
Validation Externe Intégrée + schema Côté serveur
Performance re-renders Chaque frappe Contrôlée Minimal (server-driven)
Progressive enhancement Non Non Oui

Quelles bonnes pratiques appliquer ?

  1. Gardez les Server Components par défaut : n’ajoutez 'use client' que lorsque vous avez besoin d’interactivité (état, effets, événements).

  2. Placez les directives 'use client' aux feuilles de l’arbre : les composants parents restent des Server Components tant qu’ils ne passent pas de callbacks ou de JSX interactifs.

  3. Validez toujours côté serveur : les Server Actions ne remplacent pas la validation serveur. Les données du client ne sont jamais fiables.

  4. Utilisez useOptimistic pour les interactions fréquentes : likes, commentaires, mises à jour de liste — tout ce qui bénéficie d’un retour instantané.

Conclusion

React 19 fournit une base stable pour les Server Components, les Actions et les APIs use, useActionState, useFormStatus et useOptimistic. Les APIs internes nécessaires aux frameworks et bundlers RSC peuvent toutefois encore évoluer entre versions mineures : épinglez la version de React si vous développez cette intégration vous-même.

Pour une application existante, suivez d’abord le guide officiel de migration vers React 19. Introduisez ensuite les Server Components aux frontières de lecture de données et gardez l’interactivité dans de petits Client Components. Cette migration progressive reste plus sûre qu’une réécriture complète.

Sources

Questions fréquentes

Qu'est-ce qu'un React Server Component ?

Un React Server Component est rendu avant l'envoi de l'interface au navigateur, dans un environnement séparé du client. Il peut lire des données côté serveur et son propre code n'est pas ajouté au bundle JavaScript du navigateur.

Next.js est-il obligatoire pour utiliser les React Server Components ?

Non. React ne rend pas Next.js obligatoire, mais les Server Components nécessitent l'intégration d'un framework ou d'un bundler compatible. Ils peuvent aussi s'exécuter au moment du build, sans serveur web permanent.

Comment migrer une application vers React 19 ?

Commencez par React 18.3 pour faire apparaître les avertissements de migration, puis suivez le guide officiel React 19 et ses codemods. Adoptez ensuite les Server Components progressivement, sans convertir les composants interactifs qui dépendent du navigateur.

Quelle différence entre Server Components et SSR ?

Le SSR produit le HTML initial d'une application qui peut ensuite être hydratée côté client. Les Server Components s'exécutent hors du client et transmettent leur résultat aux composants clients sans ajouter leur propre code au bundle du navigateur.