frontend / nextjs
Gestion d'erreurs
Explication
Ce que vous allez apprendre
- Isoler un plantage à un segment de route avec
error.tsx, sans casser toute l'app - Utiliser
reset()pour retenter le rendu d'un segment sans recharger la page - Distinguer les trois filets de sécurité :
not-found.tsx,error.tsx,global-error.tsx - Choisir entre
throwet un objet résultat typé pour une erreur métier attendue - Éviter de renvoyer le message brut d'une exception serveur au client
Dans quel contexte ?
Sur /dashboard/analytics, un bug dans le calcul d'un graphique fait planter le composant qui l'affiche. Sans error.tsx, toute la page blanchit d'un coup — y compris le menu de navigation et les autres widgets qui fonctionnaient très bien. Un cas différent se présente aussi sur un formulaire de mise à jour de profil : un email mal formaté n'est pas un "bug", c'est une erreur attendue qui doit s'afficher proprement sous le champ concerné, sans déclencher toute la mécanique de error.tsx prévue pour les vrais crashs.
Le problème, d'abord
Sans stratégie de gestion d'erreurs, un simple bug peut tout casser. Une exception non gérée dans un composant peut faire disparaître TOUTE l'interface, y compris les parties qui n'avaient rien à voir avec le bug (le menu, le pied de page...).
C'est une mauvaise expérience pour un problème qui aurait pu rester localisé. Next.js propose une solution nativement intégrée à son système de fichiers.
Voyons comment ça marche : un fichier error.tsx placé dans un dossier de route. Il capture automatiquement toute erreur survenant dans ce segment ET ses enfants, SANS affecter le reste de l'application.
Une image aide à retenir le principe. C'est comme une cloison coupe-feu : l'incendie reste contenu à une seule pièce, il ne se propage pas à toute la maison.
Ce fichier offre aussi un outil pour se rétablir. error.tsx reçoit une fonction reset() qui permet de retenter le rendu de ce segment précis, sans recharger toute la page.
Une fois ce mécanisme compris, il existe en réalité trois filets de sécurité, du plus précis au plus large. D'abord not-found.tsx, pour un cas métier précis : une ressource demandée n'existe pas, déclenché explicitement via notFound().
Ensuite error.tsx, déjà vu, pour les erreurs inattendues d'un segment. Et enfin global-error.tsx, le filet ultime, qui capture même les erreurs survenant dans le layout racine.
Ce dernier fichier a une particularité à connaître. Il doit redéfinir entièrement <html> et <body>, puisqu'il remplace tout, y compris le layout racine lui-même.
| Fichier | Portée | Déclenchement |
|---|---|---|
not-found.tsx | Un segment précis | Appel explicite de notFound() |
error.tsx | Le segment et ses enfants | Exception non gérée dans ce sous-arbre |
global-error.tsx | Toute l'application, layout racine inclus | Exception dans le layout racine lui-même |
Prérequis
Cette leçon suppose que tu es à l'aise avec notFound() vu dans la leçon sur les routes dynamiques : not-found.tsx en est le fichier de rendu associé.
Il reste un choix de conception important à faire : throw ou objet résultat ? Pour des erreurs "attendues", comme un email invalide dans un formulaire, throw une exception n'est pas idéal, car ça mélange erreurs de programmation et erreurs métier normales.
Le pattern "result object" ({ success: false, error: "..." }) règle ce cas. Il traite proprement les cas prévisibles, en réservant error.tsx aux VRAIS bugs inattendus.
Le piège de sécurité à ne jamais commettre : ne jamais renvoyer le message brut d'une exception serveur au client — il peut révéler des détails internes exploitables par un attaquant. Toujours logguer l'erreur côté serveur et renvoyer un message générique au client.
Piège dangereux
Renvoyer { error: err.message } directement au client peut exposer une chaîne de connexion à la base de données, un chemin de fichier interne ou la structure exacte d'une requête SQL. Toujours console.error(err) côté serveur pour le debugging, puis renvoyer un message générique comme "Une erreur interne est survenue" au client.
Commandes & code
Gestion d'erreurs
// app/dashboard/error.tsx — capture les erreurs de tout le segment "dashboard"
// DOIT être un Client Component
"use client";
import { useEffect } from "react";
export default function DashboardError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// envoyer à un service de monitoring (Sentry, etc.)
console.error("Erreur dashboard:", error.digest, error.message);
}, [error]);
return (
<div className="error-boundary">
<h2>Une erreur est survenue</h2>
<button onClick={() => reset()}>Réessayer</button>
</div>
);
}// app/global-error.tsx — ultime filet, capture même les erreurs du layout racine
// remplace ENTIÈREMENT <html> et <body>
"use client";
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<html lang="fr">
<body>
<h2>Erreur critique de l'application</h2>
<button onClick={() => reset()}>Recharger</button>
</body>
</html>
);
}// app/blog/[slug]/not-found.tsx — 404 scopé à un segment
export default function PostNotFound() {
return (
<div>
<h2>Article introuvable</h2>
<p>Cet article a peut-être été déplacé ou supprimé.</p>
</div>
);
}// Déclencher le not-found.tsx le plus proche depuis un Server Component
import { notFound } from "next/navigation";
export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await getPost(slug);
if (!post) {
notFound(); // stoppe le rendu, affiche not-found.tsx
}
return <Article post={post} />;
}
async function getPost(slug: string) {
return null;
}// Erreurs typées dans une Server Action — pattern "result object" plutôt que throw
"use server";
type ActionResult<T> = { success: true; data: T } | { success: false; error: string };
export async function updateProfile(formData: FormData): Promise<ActionResult<{ id: string }>> {
try {
const email = formData.get("email") as string;
if (!email.includes("@")) {
return { success: false, error: "Email invalide" };
}
const updated = await db.update("users").set({ email }).returning();
return { success: true, data: updated };
} catch (err) {
// erreur inattendue : ne PAS exposer err.message brut au client (fuite d'info)
console.error(err);
return { success: false, error: "Une erreur interne est survenue" };
}
}
import { db } from "@/lib/db";| Fichier | Portée | Type |
|---|---|---|
error.tsx | Segment de route et ses enfants | Client Component |
global-error.tsx | Toute l'application (racine incluse) | Client Component |
not-found.tsx | Segment, déclenché par notFound() | Server ou Client |
Résumé
error.tsxisole les crashs par segment de route, sans faire planter toute l'app.reset()retente le rendu du segment sans recharger la page entière.- Ne jamais renvoyer le message d'erreur brut d'une exception serveur au client.
- Préférer un objet résultat typé (
success/error) à unthrowpour les erreurs métier attendues.
Exercices pratiques
Mission : un graphique cassé qui blanchit tout le dashboard
Objectif : Isoler un crash au bon segment de route et corriger une fuite d'information dans une erreur renvoyée au client.
Contexte
Sur /dashboard/analytics, un bug dans le calcul d'un graphique fait planter tout le composant, et actuellement toute l'application blanchit d'un coup, y compris le menu de navigation. Il n'existe aucun error.tsx dans le projet. Par ailleurs, la Server Action updateProfile fait actuellement catch (err) { return { success: false, error: err.message } }, ce qui a récemment exposé le nom exact d'une colonne de la base de données dans un message d'erreur affiché à l'utilisateur.