Changer le thème

Guide complet Next.js Error Boundary : 5 techniques pour gérer élégamment les erreurs runtime

Easton editorial illustration: API gateway workstation

Dans le groupe ops, c’était la panique : « La page d’accueil ne s’ouvre plus ! Écran blanc partout ! »

Sur la plateforme de monitoring, un composant tiers en panne avait entraîné toute la page. L’utilisateur ne voyait qu’un blanc, sans le moindre message d’erreur. Une page qui tournait bien en prod s’effondrait parfois à cause d’un format de données incorrect ou d’un timeout API. Le try-catch classique ne couvre pas le rendu des composants React : résultat, écran blanc, fermeture de l’onglet.

Selon les études UX, un écran blanc fait fuir plus de 80 % des utilisateurs.

Heureusement, Next.js propose Error Boundary pour gérer ces erreurs runtime avec élégance : pas d’écran blanc, une UI de repli conviviale, parfois un bouton « Réessayer ». Ce guide couvre l’usage complet — de error.tsx à global-error.tsx, jusqu’aux spécificités des Server Components.

À la fin, vous saurez rendre votre app plus résiliente et éviter les réveils nocturnes pour corriger un bug en prod.

Pourquoi Error Boundary ? Les limites du traitement d’erreur classique

Au début avec React, je pensais que try-catch suffisait. La réalité m’a vite rattrapé.

Trois limites du try-catch

Première limite : il ne capture que les erreurs synchrones. JSON.parse(badData) dans un try, oui. Une erreur pendant le rendu d’un composant ? Non.

Deuxième limite : les erreurs asynchrones dans les gestionnaires d’événements. Un clic qui appelle une API en échec : le try-catch ne sert à rien, le contexte du try est déjà terminé.

Troisième limite, la plus grave : les erreurs de rendu React. Accéder à une propriété de undefined dans le return, et c’est l’écran blanc. try-catch ne protège pas.

Comment fonctionne React Error Boundary

React a introdu tôt Error Boundary. L’idée : l’arbre de composants comme des poupées russes ; l’erreur remonte jusqu’à la Error Boundary la plus proche.

Traditionnellement, un class component avec componentDidCatch et getDerivedStateFromError. Fastidieux, et incompatible avec les hooks des function components.

La solution Next.js

Avec l’App Router (Next.js 13+), Next.js encapsule Error Boundary : créez error.tsx dans un segment de route, et il devient automatiquement la limite d’erreur de ce segment. Pas de class component, pas de gestion d’état manuelle.

Point clé : Next.js gère erreurs serveur et client. Une erreur en rendu Server Component est capturée par le error.tsx le plus proche — impossible en React classique côté client seul.

Attention : error.tsx doit être un Client Component avec 'use client', car il utilise des hooks pour l’état d’erreur et la récupération.

Facebook Messenger en est l’exemple : sidebar, dialogue, saisie — chaque zone dans sa propre Error Boundary. Une zone plante, le reste continue. L’utilisateur ne remarque parfois rien.

Valeur centrale : empêcher qu’une erreur locale devienne une catastrophe globale.

Utiliser error.tsx — limite d’erreur locale

Passons à la pratique.

Structure de base : prise en main en 5 minutes

Créez error.tsx dans n’importe quel segment de route :

'use client'

import { useEffect } from 'react'

export default function Error({
  error,
  reset
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  useEffect(() => {
    // Envoyer l'erreur à Sentry ou autre plateforme
    console.error('Erreur capturée:', error)
  }, [error])

  return (
    <div className="flex flex-col items-center justify-center min-h-screen p-4">
      <h2 className="text-2xl font-bold mb-4">Oups, un problème est survenu</h2>
      <p className="text-gray-600 mb-4">
        {error.message || 'Échec du chargement de la page'}
      </p>
      <button
        onClick={() => reset()}
        className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
      >
        Réessayer
      </button>
    </div>
  )
}

Points essentiels :

  1. 'use client' obligatoire en tête de fichier
  2. Objet error : message, stack, et digest (Next.js 15) pour le suivi
  3. reset : re-rend le contenu sous la limite d’erreur — une chance de récupération pour l’utilisateur

Mécanisme de remontée : couche par couche

app/
├── layout.tsx          # layout racine
├── error.tsx           # erreurs sous la racine (A)
├── page.tsx            # page d'accueil
├── dashboard/
│   ├── layout.tsx      # layout dashboard
│   ├── error.tsx       # erreurs dashboard (B)
│   └── page.tsx        # page dashboard
└── profile/
    └── page.tsx        # page profile

Si dashboard/page.tsx plante → capturé par (B), le error.tsx parent le plus proche.

Si profile/page.tsx plante → pas d’error.tsx dans profile → remonte jusqu’à (A).

Piège : error.tsx ne capture pas les erreurs du layout.tsx du même niveau. La limite est à l’intérieur du layout ; si le layout échoue avant le chargement de la limite, il faut un error.tsx plus haut (ex. app/error.tsx pour dashboard/layout.tsx).

Bon usage de reset()

reset re-rend l’arbre sous la limite d’erreur. Adapté aux erreurs temporaires :

  • timeout API (retry possible)
  • réseau instable
  • conditions limites liées à la saisie

Pour un bug (undefined.property), retry inutile : corriger le code et déployer.

Certaines équipes limitent à 3 tentatives puis orientent vers rafraîchissement ou support :

'use client'

import { useEffect, useState } from 'react'

export default function Error({ error, reset }: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  const [retryCount, setRetryCount] = useState(0)

  const handleReset = () => {
    setRetryCount(prev => prev + 1)
    reset()
  }

  return (
    <div>
      <h2>Une erreur est survenue</h2>
      {retryCount < 3 ? (
        <button onClick={handleReset}>
          Réessayer ({retryCount}/3)
        </button>
      ) : (
        <p>Plusieurs tentatives ont échoué. Actualisez la page ou <a href="/contact">contactez-nous</a></p>
      )}
    </div>
  )
}
40 %
Un bouton de retry permet de récupérer environ 40 % des erreurs temporaires

global-error.tsx — filet de sécurité global

error.tsx est puissant, mais il ne capture pas les erreurs du layout racine app/layout.tsx. C’est le rôle de global-error.tsx.

Quand l’utiliser ?

Peu déclenché en production. Principalement :

  1. échec d’initialisation du layout racine (ex. lib de state global)
  2. erreurs non capturées par aucun error.tsx

Dernier filet de sécurité : on espère ne jamais en avoir besoin, mais il doit exister.

Spécificités de global-error.tsx

Contrairement à error.tsx ordinaire, il doit inclure toute la structure HTML : balises <html> et <body>.

Pourquoi ? Il remplace entièrement le layout racine. Si le layout racine plante, plus de squelette de page — global-error.tsx reconstruit une page minimale viable.

'use client'

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <html>
      <body>
        <div style={{
          display: 'flex',
          flexDirection: 'column',
          alignItems: 'center',
          justifyContent: 'center',
          minHeight: '100vh',
          padding: '20px',
          fontFamily: 'system-ui, sans-serif'
        }}>
          <h1>L'application a rencontré un problème grave</h1>
          <p style={{ color: '#666', marginBottom: '20px' }}>
            {process.env.NODE_ENV === 'development'
              ? error.message
              : 'Nous traitons ce problème, veuillez réessayer plus tard'}
          </p>
          <button
            onClick={() => reset()}
            style={{
              padding: '10px 20px',
              background: '#0070f3',
              color: 'white',
              border: 'none',
              borderRadius: '5px',
              cursor: 'pointer'
            }}
          >
            Recharger l'application
          </button>
        </div>
      </body>
    </html>
  )
}

Styles inline plutôt que Tailwind ou CSS modules : le design system peut ne pas être chargé.

Développement vs production

global-error.tsx ne s’active qu’en production. En dev, Next.js affiche la page d’erreur rouge avec stack pour le debug.

En prod, masquez les détails techniques. Le test process.env.NODE_ENV ci-dessus sert à ça. L’utilisateur veut savoir si l’app est utilisable, pas lire TypeError: Cannot read property 'map' of undefined.

Faut-il l’ajouter ?

Oui. Probabilité faible, impact énorme. Mieux vaut une page d’erreur digne que « impossible d’accéder à ce site » du navigateur.

Comme une assurance : on n’y pense pas, jusqu’au jour où elle compte.

Server Components : points d’attention

Les Server Components (Next.js 13+) changent la donne : erreurs serveur et client ne se traitent pas pareil.

Où vont les erreurs des Server Components ?

Au début, je me demandais : erreur au rendu serveur, est-ce que error.tsx côté client capte ?

Oui. Next.js transmet l’erreur au client et déclenche le error.tsx le plus proche. En production, les détails sont masqués (sécurité).

Échec de connexion BDD : stack complète en dev, message générique en prod.

Erreurs attendues vs inattendues

Distinction importante dans la doc officielle :

Erreurs attendues (logique métier, traitement explicite) :

  • validation de formulaire
  • API 404
  • permissions insuffisantes

Erreurs inattendues (bugs, infra → Error Boundary) :

  • panne base de données
  • service tiers indisponible
  • accès à une propriété de undefined

Pour les attendues, try-catch dans Server Action ou fetch, puis retour d’erreur au composant :

// app/actions.ts
'use server'

export async function createUser(formData: FormData) {
  const email = formData.get('email') as string

  // Erreur attendue : format email invalide
  if (!email.includes('@')) {
    return { error: 'Veuillez saisir une adresse e-mail valide' }
  }

  try {
    await db.user.create({ email })
    return { success: true }
  } catch (error) {
    // Erreur inattendue : BDD — laisser Error Boundary gérer
    throw new Error('Échec de la création utilisateur')
  }
}

Pour les inattendues : throw et laisser remonter vers error.tsx.

Erreurs lors de la récupération de données

// app/posts/page.tsx
async function getPosts() {
  const res = await fetch('https://api.example.com/posts')

  // Erreur attendue : statut HTTP d'erreur
  if (!res.ok) {
    if (res.status === 404) {
      return { posts: [], error: 'Aucune donnée pour le moment' }
    }
    // Erreur serveur — Error Boundary
    throw new Error('Échec de la récupération des données')
  }

  return { posts: await res.json() }
}

export default async function PostsPage() {
  const { posts, error } = await getPosts()

  if (error) {
    return <div>Aucun article pour le moment</div>
  }

  return (
    <ul>
      {posts.map(post => <li key={post.id}>{post.title}</li>)}
    </ul>
  )
}

« Aucune donnée » n’a pas besoin d’une page d’erreur complète ; seules les vraies pannes système déclenchent error.tsx.

Utilité de error.digest

Next.js 15 ajoute digest, identifiant unique auto-généré.

Scénario : l’utilisateur voit l’erreur, envoie une capture au support avec le digest ; le support retrouve la requête exacte dans les logs.

'use client'

export default function Error({ error }: { error: Error & { digest?: string }}) {
  return (
    <div>
      <h2>Une erreur est survenue</h2>
      <p>Référence : {error.digest}</p>
      <p>Contactez le support avec ce numéro</p>
    </div>
  )
}

Avec Sentry ou équivalent, le digest accélère fortement le diagnostic.

Bonnes pratiques en production

1. Limites d’erreur granulaires

Un seul error.tsx à la racine ne suffit pas. Isolez les zones critiques.

Exemple e-commerce :

app/
├── error.tsx                    # filet global
├── (shop)/
│   ├── products/
│   │   └── error.tsx           # liste produits isolée
│   ├── cart/
│   │   └── error.tsx           # panier isolé
│   └── checkout/
│       └── error.tsx           # checkout isolé

Panier en panne → navigation produits possible.

2. Monitoring et reporting

useEffect dans error.tsx est l’endroit idéal :

'use client'

import { useEffect } from 'react'
import * as Sentry from '@sentry/nextjs'

export default function Error({ error, reset }: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  useEffect(() => {
    Sentry.captureException(error, {
      tags: {
        errorDigest: error.digest,
        errorBoundary: 'app-root'
      },
      extra: {
        userAgent: navigator.userAgent,
        timestamp: new Date().toISOString()
      }
    })
  }, [error])

  return (
    // UI d'erreur...
  )
}

Incluez digest et contexte navigateur. Certaines équipes journalisent aussi les 5 dernières pages visitées.

3. UI d’erreur conviviale

Les utilisateurs veulent savoir :

  • que s’est-il passé ? (langage simple)
  • que faire ? (actions claires)
  • mes données sont-elles perdues ? (périmètre d’impact)
return (
  <div className="error-container">
    <h2>Échec du chargement de la page</h2>
    <p>Le réseau est peut-être instable, ou nos serveurs font une pause</p>

    <div className="actions">
      <button onClick={reset}>Réessayer</button>
      <a href="/">Retour à l'accueil</a>
      <a href="/help">Contacter le support</a>
    </div>

    <details className="error-details">
      <summary>Informations techniques (optionnel)</summary>
      <code>{error.digest}</code>
    </details>
  </div>
)

« Nos serveurs font une pause » bat « 500 Internal Server Error » pour l’anxiété utilisateur.

4. Stratégie de retry intelligente

  • délai : attendre 1–2 s avant reset
  • backoff exponentiel : 1 s, 2 s, 4 s…
  • type d’erreur : retry pour réseau, support direct pour bug de code
const [retryCount, setRetryCount] = useState(0)
const [isRetrying, setIsRetrying] = useState(false)

const handleReset = async () => {
  setIsRetrying(true)
  setRetryCount(prev => prev + 1)

  await new Promise(resolve =>
    setTimeout(resolve, Math.pow(2, retryCount) * 1000)
  )

  setIsRetrying(false)
  reset()
}

5. Différencier les environnements

const isDev = process.env.NODE_ENV === 'development'

return (
  <div>
    <h2>{isDev ? error.message : 'Un problème est survenu'}</h2>

    {isDev && (
      <pre>
        <code>{error.stack}</code>
      </pre>
    )}

    {!isDev && (
      <p>Nous avons enregistré ce problème et le corrigerons rapidement</p>
    )}
  </div>
)

Dev : stack complète. Prod : message sobre, pas de fuite technique.

6. Ne pas en abuser

Error Boundary = filet, pas stratégie principale.

Erreurs attendues → try-catch. Dégradation locale possible → pas de page d’erreur globale.

Avatar qui ne charge pas → avatar par défaut, pas toute la page profil en erreur.

Réservez Error Boundary aux surprises qu’on ne peut pas isoler localement.

Conclusion

Trois idées à retenir :

Première : Error Boundary n’est pas optionnel. La fuite due à l’écran blanc est sous-estimée ; quelques fichiers error.tsx évitent bien des alertes nocturnes.

Deuxième : traitement en couches. error.tsx pour le local, global-error.tsx en dernier recours, distinction attendu/inattendu dans les Server Components.

Troisième : l’UX d’abord. Détails techniques → monitoring. Côté utilisateur : message clair et actions. Le bouton retry récupère ~40 % des erreurs temporaires — excellent ROI.

Ajoutez error.tsx à la racine, puis aux zones critiques. Couplez avec Sentry : la stabilité perçue progresse vite.

Et n’oubliez pas global-error.tsx — rarement utilisé, indispensable comme une ceinture de sécurité.

Implémenter Error Boundary dans Next.js

Ajouter des limites d'erreur à une application Next.js pour gérer élégamment les erreurs runtime

  1. 1

    Step 1: Créer le fichier error.tsx

    Créez error.tsx dans app ou dans n'importe quel segment de route, avec la directive 'use client'
  2. 2

    Step 2: Implémenter le composant de gestion d'erreur

    Définissez le composant Error avec les paramètres error et reset, et concevez une UI d'erreur conviviale
  3. 3

    Step 3: Ajouter le reporting d'erreurs

    Dans useEffect, envoyez l'erreur à Sentry ou une plateforme similaire et enregistrez error.digest
  4. 4

    Step 4: Implémenter une nouvelle tentative intelligente

    Ajoutez un bouton de retry, limitez le nombre de tentatives, mécanisme de récupération pour erreurs temporaires
  5. 5

    Step 5: Créer global-error.tsx

    Créez global-error.tsx dans app comme dernier filet, avec une structure HTML complète
  6. 6

    Step 6: Distinguer les types d'erreur

    Dans les Server Components, séparez erreurs attendues (traitement explicite) et inattendues (Error Boundary)

FAQ

Quelle différence entre error.tsx et global-error.tsx ?
error.tsx capture les erreurs au niveau du segment de route, mais pas celles du layout.tsx du même niveau. global-error.tsx est le dernier filet : il capture les erreurs du layout racine, doit inclure les balises html et body complètes, et ne s'active qu'en production.
Pourquoi error.tsx doit-il être un Client Component ?
Parce qu'error.tsx utilise des hooks React (comme useEffect) pour gérer l'état d'erreur et la récupération, et les hooks ne fonctionnent que dans les Client Components. Il faut donc 'use client' en tête de fichier.
Les erreurs des Server Components sont-elles capturées par error.tsx ?
Oui. Next.js transmet les informations d'erreur serveur au client et déclenche le error.tsx le plus proche. En production, les détails sont masqués pour ne pas exposer d'informations sensibles du serveur.
Quand utiliser try-catch plutôt qu'Error Boundary ?
Les erreurs métier attendues (validation de formulaire, API 404, permissions insuffisantes) doivent être gérées explicitement avec try-catch. Error Boundary reste pour les erreurs inattendues (bug de code, échec de connexion base de données, panne d'un service tiers).
Comment fonctionne la fonction reset() ?
reset() re-rend l'arbre des composants enfants sous la limite d'erreur. Elle convient aux erreurs temporaires (timeout réseau, échec de chargement de ressource). Pour un bug de code, le retry ne suffit pas : il faut corriger et redéployer.

10 min de lecture · Publié le: 6 janv. 2026 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog