Retour au blog

React 19 Server Components en pratique

20 janvier 2026 Dedimarco
react javascript web server-components
React 19 Server Components en pratique

React 19 Server Components en pratique

React 19 est sorti en version stable le 5 décembre 2024, marquant la plus grande évolution du framework depuis l’introduction des Hooks en 2018. Les Server Components, longtemps restés dans le canal expérimental, sont désormais stables et prêts pour la production. Ce guide pratique vous montre comment les utiliser concrètement dans vos projets.

Server Components : le paradigme expliqué

Les Server Components représentent un changement fondamental dans la façon dont React gère le rendu. Contrairement aux composants classiques qui s’exécutent dans le navigateur, les Server Components s’exécutent uniquement côté serveur — ou au moment du build — et n’envoient aucun JavaScript au client.

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 les frameworks compatibles comme Next.js 14+. Pour créer un Client Component (le comportement classique), vous ajoutez la directive 'use client' en haut du fichier.

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
}

Actions : la nouvelle gestion des formulaires

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

Server Actions avec use server

Les Server Actions sont des fonctions asynchrones avec la directive 'use server' qui s’exécutent côté serveur lorsqu’elles sont appelées depuis le client :

// 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.

Le hook use() : lire des ressources pendant le rendu

Le hook use() est une nouveauté qui casse la règle habituelle des hooks : il peut être appelé conditionnellement et à l’intérieur de boucles. Il permet de lire des Promises et du Context de manière suspendable.

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>
  );
}

useOptimistic : retours immédiats à l’utilisateur

Le hook useOptimistic permet des mises à jour optimistes de l’interface : l’UI se met à jour immédiatement avant la confirmation du serveur, puis revient automatiquement en cas d’erreur.

'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>
  );
}

Comparatif des approches

CritèreuseState classiqueReact Hook FormReact 19 (Actions)
BoilerplateÉlevéModéréMinimal
Gestion pending/erreurManuelleIntégréeIntégrée (useActionState)
Mises à jour optimistesManuellesNonOui (useOptimistic)
ValidationExterneIntégrée + schemaCôté serveur
Performance re-rendersChaque frappeContrôléeMinimal (server-driven)
Progressive enhancementNonNonOui

Bonnes pratiques

  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 représente une évolution majeure dans la façon de construire des applications. Les Server Components réduisent le JavaScript envoyé au client, les Actions simplifient la gestion des formulaires, et les nouveaux hooks (use, useOptimistic) rendent les patterns asynchrones plus naturels.

La courbe d’apprentissage existe, surtout pour les développeurs habitués au modèle “tout côté client”. Mais les bénéfices en termes de performance, d’expérience utilisateur et de simplicité du code justifient l’investissement. Combinés à un framework comme Next.js, ces changements ouvrent la voie à des applications web plus rapides et plus accessibles.