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è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 |
Bonnes pratiques
-
Gardez les Server Components par défaut : n’ajoutez
'use client'que lorsque vous avez besoin d’interactivité (état, effets, événements). -
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. -
Validez toujours côté serveur : les Server Actions ne remplacent pas la validation serveur. Les données du client ne sont jamais fiables.
-
Utilisez
useOptimisticpour 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.