Next.js
React
Server Actions
Fullstack
Web Development

Next.js 15 et actions serveur : le guide définitif des mutations modernes

Next.js 15 et actions serveur : le guide définitif des mutations modernes

Le développement Web est en constante évolution. Il fut un temps où tout était rendu sur le serveur (PHP, Ruby on Rails). Ensuite, nous déplaçons le tout vers le client (SPA avec React, Vue). Aujourd'hui, avec l'avènement de React Server Components (RSC) et de Next.js App Router, nous trouvons un juste milieu : le meilleur des deux mondes.

Et le joyau de cette nouvelle ère dans l'écosystème React sont les Server Actions.

Avec la sortie de Next.js 15, les actions serveur ne sont plus expérimentales et sont devenues la norme recommandée pour gérer les mutations de données. Dans ce guide volumineux, nous explorerons tous les détails de cette fonctionnalité, des bases aux normes avancées de sécurité et d'UX. Si vous écrivez encore pages/api/submit.ts pour traiter des formulaires, préparez-vous à supprimer une grande partie du code passe-partout.

Que sont les actions du serveur, de toute façon ?

En termes simples, Les actions serveur sont des fonctions asynchrones qui s'exécutent sur le serveur, mais peuvent être invoquées directement à partir de composants client ou serveur.

Auparavant, pour soumettre un formulaire, le flux était :

  1. Créez un composant de formulaire sur le client ("use client").
  2. Créez un état useState pour les entrées.
  3. Créez une fonction onSubmit qui fait un fetch('/api/submit', ...).
  4. Créez un fichier API Route (pages/api/submit.ts) pour recevoir la requête, validez-le et enregistrez-le dans la base de données.
  5. Gérez manuellement les erreurs, les états de chargement et la revalidation des données.

Avec Actions serveur, le flux est le suivant :

  1. Créez une fonction async qui enregistre dans la banque.
  2. Passez cette fonction à la prop action de <form>.

Fin. Next.js s'occupe de la communication, de la sérialisation et de l'exécution. Il s'agit d'un appel de procédure à distance (RPC) parfaitement adapté au Web.

Configuration initiale dans Next.js 15

Dans Next.js 15, les actions serveur sont déjà activées par défaut. Vous n'avez pas besoin de changer le next.config.js.

La convention est simple :

  • Pour définir des actions pouvant être importées dans les composants Client, créez un fichier avec la directive "use server" en haut.
  • Pour les actions en ligne dans les composants serveur, ajoutez "use server" à l'intérieur de la fonction.

Exemple de base : le "Hello World" des actions

Créons une action pour enregistrer un article de blog. Créez un fichier src/app/actions.ts :

// src/app/actions.ts 'use server' import { db } from '@/lib/db' import { revalidatePath } from 'next/cache' export async function createPost(formData: FormData) { const title = formData.get('title') as string const content = formData.get('content') as string // Validação básica (em produção use Zod!) if (!title || !content) { throw new Error('Campos obrigatórios faltando') } // Interação direta com o banco (sem API Routes!) await db.post.create({ data: { title, content } }) // A mágica: atualiza a UI instantaneamente revalidatePath('/blog') }

Et dans votre composant :

// src/components/create-post-form.tsx import { createPost } from '@/app/actions' export function CreatePostForm() { return ( <form action={createPost} className="p-4 border rounded"> <input name="title" placeholder="Título" className="border p-2 mb-2 w-full" /> <textarea name="content" placeholder="Conteúdo" className="border p-2 w-full" /> <button type="submit" className="bg-blue-500 text-white p-2 rounded"> Salvar Post </button> </form> ) }

Notez l'absence de JavaScript sur le client pour la soumission. Ce formulaire fonctionne même si l'utilisateur désactive JS dans le navigateur (Progressive Enhancement), bien que dans les applications modernes, nous en dépendions rarement.

Gestion de l'état de charge (useFormStatus)

Une mauvaise UX clique sur "Enregistrer" et ne reçoit pas de commentaires. Puisque nous utilisons la prop HTML native action, nous n'avons pas de manuel useState(loading). React nous fournit le hook useFormStatus pour cela.

Remarque : useFormStatus doit être utilisé dans un composant rendu dans <form>.

// src/components/submit-button.tsx 'use client' import { useFormStatus } from 'react-dom' export function SubmitButton() { const { pending } = useFormStatus() return ( <button type="submit" disabled={pending} className="bg-blue-500 disabled:bg-gray-400 text-white p-2 rounded flex items-center gap-2" > {pending ? 'Salvando...' : 'Salvar Post'} {pending && <Spinner />} </button> ) }

Maintenant, utilisez simplement <SubmitButton /> à l'intérieur du formulaire de l'exemple précédent.

Gestion des erreurs et commentaires (useActionState)

Et si base de données échoue ? Ou une validation ? Le simple fait de lancer une erreur (throw new Error) n'est pas la meilleure UX. Nous souhaitons renvoyer des messages d'erreur au formulaire.

Pour ce faire, nous utilisons le hook useActionState (anciennement useFormState dans React expérimental). Il permet à l'action serveur de renvoyer une valeur mise à jour sur le client.

Refactoriser notre action :

// src/app/actions.ts 'use server' export type FormState = { message: string; errors?: { title?: string[]; content?: string[]; }; } export async function createPostSafely(prevState: FormState, formData: FormData): Promise<FormState> { // Simulação de validação com Zod const validatedFields = schema.safeParse({ title: formData.get('title'), content: formData.get('content'), }); if (!validatedFields.success) { return { message: 'Erro de validação', errors: validatedFields.error.flatten().fieldErrors } } try { await db.post.create({ data: validatedFields.data }) } catch (e) { return { message: 'Erro ao salvar no banco de dados' } } revalidatePath('/blog') return { message: 'Post criado com sucesso!' } }

Et dans le composant client :

'use client' import { useActionState } from 'react' import { createPostSafely } from '@/app/actions' const initialState = { message: '', errors: {} } export function AdvancedForm() { const [state, formAction] = useActionState(createPostSafely, initialState) return ( <form action={formAction}> {state.message && <p className="text-red-500">{state.message}</p>} <input name="title" /> {state.errors?.title && <p className="text-red-500 text-sm">{state.errors.title[0]}</p>} {/* ... restante do form ... */} </form> ) }

Revalidation du cache : la puissance de revalidatePath

Dans l'ancien modèle, après une mutation, il fallait récupérer les données manuellement ou utiliser des bibliothèques comme React Query ou SWR pour invalider les caches.

Dans Next.js, ceci est intégré. La fonction revalidatePath(path) efface le cache pour cette route spécifique. Lors de la prochaine visite (ou immédiatement, s'il s'agit d'une navigation SPA), Next.js récupère les nouvelles données du serveur. Il existe également revalidateTag(tag), qui vous donne un contrôle granulaire si vous utilisez fetch avec (fetch(url, { next: { tags: ['posts'] } })) balises.

Mises à jour optimistes (useOptimistic)

Pour les applications qui semblent « instantanées », nous ne voulons pas attendre que le serveur réponde pour mettre à jour l’interface utilisateur. Si j'ajoute un élément à la liste, je veux le voir là maintenant.

Le crochet useOptimistic permet cela de manière élégante.

'use client' import { useOptimistic } from 'react' export function PostList({ posts }: { posts: Post[] }) { const [optimisticPosts, addOptimisticPost] = useOptimistic( posts, (state, newPost: Post) => [newPost, ...state] ); async function action(formData: FormData) { const title = formData.get('title') as string; // Atualiza a UI imediatamente addOptimisticPost({ id: Math.random(), title, content: '...' }); // Chama a Server Action real await createPost(formData); } return ( <div> <form action={action}>...</form> <ul> {optimisticPosts.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> </div> ) }

Si l'action du serveur échoue, React ramène automatiquement l'état optimiste à l'état réel précédent. C'est robuste et magique.

Sécurité : l'éléphant dans la pièce

De nombreux développeurs examinent "use server" et se demandent : « Attendez, est-ce que j'expose ma base de données au client ?

Non. Le code d'action du serveur n'est jamais envoyé au navigateur. Seule la « signature » de la fonction (une URL de référence Next.js interne) est exposée. Cependant, il est crucial de traiter les actions du serveur comme des points d'entrée publics à votre API.

Liste de contrôle de sécurité obligatoire :

  1. Authentification : vérifiez toujours qui appelle l'action. Ne présumez pas que l'utilisateur est connecté simplement parce que le bouton "Enregistrer" était visible.

    import { auth } from '@/auth' // Auth.js ou Clerk export async function deletePost(id: string) { const session = await auth() if (!session?.user) throw new Error('Não autorizado') // ... }
  2. Autorisation : L'utilisateur est connecté, mais peut-il *supprimer ce message ?

    const post = await db.post.findUnique({ where: { id } }) if (post.authorId !== session.user.id) throw new Error('Proibido')
  3. Validation d'entrée : Ne faites jamais confiance à FormData ou aux arguments transmis. Utilisez Zod pour vous assurer que les données sont au bon format.

Actions du serveur par rapport aux routes API

Après tout, les routes API sont-elles mortes ?

Pas exactement, mais son utilisation a considérablement diminué.

Utilisez Actions du serveur dans les cas suivants :

  • Vous avez affaire à des mutations de forme.
  • Vous souhaitez appeler une fonction serveur à partir d'un événement sur le client (onClick).
  • Vous souhaitez une saisie TypeScript de bout en bout sans générer de SDK.

Utilisez Routes API (gestionnaires de routes) lorsque :

  • Vous devez exposer une [REST API47 publique à des tiers (webhooks, applications mobiles).
  • Vous avez besoin de fonctionnalités spécifiques du protocole HTTP que les abstractions React cachent (par exemple, streaming de binaires personnalisés, en-têtes complexes).

Conclusion

Next.js 15 consolide les actions serveur comme l'un des changements les plus productifs du développement Web récent. Ils éliminent la couche intermédiaire de « code de colle » (routes API, récupérateurs, gestionnaires d'état) et vous permettent de vous concentrer sur ce qui compte : la logique métier et l'interface utilisateur.

En combinant les actions serveur avec useActionState, useOptimistic et la validation avec Zod, vous créez des applications robustes et sécurisées avec une expérience utilisateur de classe mondiale, en écrivant une fraction du code que vous auriez écrit il y a 3 ans.

L’avenir est côté serveur, mais l’expérience est côté client. Et les actions serveur sont le pont entre elles.


Avez-vous déjà migré vos formulaires vers Server Actions ? Quel a été le plus grand défi ? Laissez votre commentaire !

A lire aussi