Comment gérer les erreurs côté serveur
Gestion des erreurs 404 et ErrorBoundary : apprends à sécuriser tes routes dynamiques React Router 7 et à renvoyer des réponses HTTP propres.
Dans React Router 7, chaque route peut renvoyer une erreur contrôlée depuis le loader ou l’action sans planter toute l'application. Au lieu d’afficher une page blanche, on renvoie un objet Response avec un code HTTP et un message, et on intercepte ce cas dans un ErrorBoundary dédié. Ce pattern améliore l’UX et facilite le débogage.
1. Créer une route dynamique
On part d’une route qui affiche le détail d’un utilisateur à partir de son slug :
1import {2isRouteErrorResponse,3useLoaderData,4useRouteError,5type LoaderFunctionArgs,6} from 'react-router'7import { getUserBySlug } from '~/users.server'89export async function loader({ params }: LoaderFunctionArgs) {10const user = await getUserBySlug({ slug: params.userSlug })11if (!user) {12// ^? On lève une Response pour renvoyer une erreur HTTP contrôlée13// @callout: throw new Response déclenche isRouteErrorResponse()14throw new Response(`User "${params.userSlug}" was not found`, {15status: 404,16statusText: 'Not Found',17})18}19return { user }20}2122export default function UserDetail() {23const { user } = useLoaderData<typeof loader>()24return (25<div className="p-4">26<h1 className="text-2xl font-bold">{user.name}</h1>27<p>ID: {user.id}</p>28</div>29)30}
- On importe
LoaderFunctionArgsetuseLoaderDatadepuis<Link to="https://reactrouter.com/api/hooks">react-router</Link>. - Si l’utilisateur n’existe pas, on throw une instance de
Responseavec unstatusetstatusText. - React Router retient ce
Responseet le transmet à l’ErrorBoundary.
2. Personnaliser l’ErrorBoundary de la route
On ajoute un export ErrorBoundary dans le même fichier pour capturer uniquement les erreurs de cette route :
1export function ErrorBoundary() {2const error = useRouteError()34if (isRouteErrorResponse(error)) {5return (6<div className="h-screen flex flex-col items-center justify-center p-4">7<h1 className="text-3xl font-bold text-red-600">8{error.status} {error.statusText}9</h1>10<p className="mt-2 text-red-500">{error.data}</p>11</div>12)13}1415if (error instanceof Error) {16return (17<div className="h-screen flex flex-col items-center justify-center p-4">18<h1 className="text-3xl font-bold text-red-600">Erreur</h1>19<p className="mt-2 text-red-500">{error.message}</p>20</div>21)22}2324return <h1>Une erreur inconnue est survenue.</h1>25}
useRouteError()rend l’erreur levée par le loader/action.isRouteErrorResponse(error)repère lesResponsecontrôlées.- En mode
development, tu peux aussi afficher la stack trace.
Pourquoi un ErrorBoundary par route ?
Cela évite de dupliquer les messages d’erreur génériques et de bloquer l’ensemble de l’application pour un simple détail manquant.
3. Gérer le 404 et les autres statuts
Dans users.server.ts, la fonction getUserBySlug renvoie undefined si pas trouvé :
1const db = {2users: [3{ id: 1, name: 'Virgile', slug: 'virgile' },4{ id: 2, name: 'Robert', slug: 'robert' },5// …6]7}89export async function getUserBySlug({ slug }: { slug: string }) {10// Simule un délai réseau11await new Promise(r => setTimeout(r, 100))12return db.users.find(u => u.slug === slug)13}
Le loader l’appelle, puis génère un 404 pour un slug manquant. Pour d’autres erreurs (500, 403), le pattern est identique : on lève une Response avec le code adapté.
4. Fallback global dans root.tsx
Pour toutes les erreurs non capturées localement, on a un ErrorBoundary global dans app/root.tsx :
1import { isRouteErrorResponse, Link, Outlet } from 'react-router'23export function ErrorBoundary({ error }: { error: unknown }) {4let title = 'Oops!'5let message = 'Une erreur inattendue est survenue.'67if (isRouteErrorResponse(error)) {8title = error.status === 404 ? '404 – Introuvable' : `Erreur ${error.status}`9message = error.data || error.statusText || message10} else if (error instanceof Error) {11message = error.message12}1314return (15<div className="container mx-auto p-4">16<h1 className="text-4xl font-bold text-red-600 mb-2">{title}</h1>17<p className="text-lg text-gray-700">{message}</p>18<Link to="/" className="text-blue-500 hover:underline mt-4 block">19Retour à l’accueil20</Link>21</div>22)23}
Tip
En production, masque la stack trace pour ne pas exposer d’informations sensibles. Garde les messages concis et utiles.
Points clés
- Loader vs. action : utilisez
throw new Response(...)dans loader/action
pour renvoyer un statut HTTP contrôlé (404, 403, 500…). - ErrorBoundary : déclarez-en un par route pour personnaliser le rendu
ou un global dansroot.tsxpour attraper le reste. - useRouteError + isRouteErrorResponse : distinguez facilement
vos erreurs contrôlées des exceptions inattendues. - En mode dev, affichez la stack trace ; en prod, simplifiez l’affichage.
Exercices rapides
- Ajouter une vérification d’autorisation dans
loader:- Si l’utilisateur n’a pas un rôle
"admin", throw uneResponse403. - Affichez un message personnalisé dans l’ErrorBoundary local.
- Si l’utilisateur n’a pas un rôle
- Gérer une erreur serveur inattendue :
- Dans
getUserBySlug, lancezthrow new Error("DB inaccessible"). - Vérifiez que le ErrorBoundary global intercepte et affiche un message générique.
- Dans
- Créer une route
/settings:- Si la fonctionnalité est désactivée, renvoyez un
Response410 (Gone). - Affichez un message dédié expliquant la suppression de la page.
- Si la fonctionnalité est désactivée, renvoyez un
Comprendre les concepts fondamentaux
Quelle est la principale différence entre les composants client et serveur dans React ?
Optimisation des performances
Quelle technique est recommandée pour éviter les rendus inutiles dans React ?
Architecture des données
Quel hook permet de gérer les effets de bord dans un composant React ?