Pages d'erreur Next.js 404 et 500 : guide complet de l'implémentation au design

Vendredi à 15 h, le chef de produit a posté sur Slack : « Est-ce vraiment notre site ? C’est affreux. »
La capture d’écran : texte blanc et noir, “404 Cette page est introuvable.” Embarrassant.
Ensuite, la métrique est arrivée : environ 40 % des utilisateurs qui ont atteint le 404 par défaut ont fermé l’onglet.
Sur les projets Next.js, nous peaufinons le chemin heureux : accueil, listes, pages de détails. Les pages d’erreur semblent rares, elles sont donc ignorées.
Ce numéro m’a fait changer d’avis. Un lien brisé est souvent la dernière chance pour quelqu’un de rester. Une simple “page introuvable” sans navigation, sans recherche, sans indice n’inspire pas confiance.
App Router vous donne « not-found.tsx », « error.tsx » et « global-error.tsx ». Cela semble simple ; les pièges sont bien réels.
Mon premier passage a renvoyé HTTP 200 sur les 404, afin que Google ne les traite pas comme des pages manquantes. Une autre fois, les styles « global-error.tsx » n’ont jamais été appliqués : il s’avère qu’il ne peut pas importer de modules CSS comme vous le souhaiteriez.
Cette procédure pas à pas couvre ces fichiers de bout en bout, ainsi qu’un 404 qui aide réellement les gens à rester. Exemples complets ; les pièges sont ceux que je frappe.
Comprendre les mécanismes de gestion des erreurs Next.js
Lorsque j’ai rencontré App Router pour la première fois, je n’arrivais pas à comprendre les différences entre ces trois fichiers. not-found.tsx, error.tsx, global-error.tsx — noms similaires, objectifs complètement différents.
Division du travail
En termes simples :
- not-found.tsx - Gère les erreurs 404 lorsque les pages n’existent pas
- error.tsx - Gère les erreurs d’exécution telles que les échecs de chargement des données ou les plantages de code
- global-error.tsx - Repli de dernier recours, se déclenche même en cas d’échec de la configuration racine
Vous pourriez vous demander pourquoi trois fichiers ? Un seul « error.tsx » ne suffirait-il pas ?
Voici le truc. La gestion des erreurs Next.js est hiérarchique, comme les poupées gigognes russes. error.tsx ne peut détecter que les erreurs des routes sœurs et enfants — il ne peut pas détecter les erreurs de son propre layout.tsx. Que se passe-t-il si la disposition racine est cassée ? C’est là qu’intervient « global-error.tsx ».
Quant à « not-found.tsx », il a un statut spécial — une priorité plus élevée que « error.tsx ». Lorsque vous appelez activement la fonction notFound(), Next.js ignore error.tsx et restitue directement not-found.tsx.
Le placement des fichiers est important
Les trois fichiers peuvent être placés à différents niveaux de route, et l’emplacement détermine leur portée.
Fichiers d’erreur au niveau racine (dans le répertoire app/) :```
app/
├── layout.tsx
├── not-found.tsx ← Page 404 globale
├── error.tsx ← Gestionnaire d’erreurs global
├── global-error.tsx ← Filet du layout racine
└── page.tsx
**Fichiers d'erreurs au niveau de l'itinéraire** (dans des itinéraires spécifiques) :```
app/
├── blog/
│ ├── [slug]/
│ │ ├── page.tsx
│ │ ├── not-found.tsx ← 404 dédié au blog
│ │ └── error.tsx ← Page d'erreur dédiée au blog
Si un utilisateur visite « /blog/nonexistent-article », Next.js donne la priorité à « app/blog/[slug]/not-found.tsx » par rapport à la racine « app/not-found.tsx ». Cela vous permet de personnaliser les pages d’erreur pour différentes sections.
La fonction notFound() : déclencheurs programmatiques 404
Avoir un fichier « not-found.tsx » ne suffit pas : vous devez savoir quand le déclencher.
Scénario le plus courant : récupérer des données par ID, mais les données n’existent pas.
// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation'
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`)
if (!res.ok) return null
return res.json()
}
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug)
if (!post) {
notFound() // Déclencher not-found.tsx
}
return <article>{post.title}</article>
}
Attention à ce piège : vous devez appeler notFound() avant de renvoyer un JSX. Si vous avez déjà renvoyé un contenu partiel, la diffusion en continu a démarré et le code d’état HTTP se verrouille à 200 et non à 404.
Je suis tombé dans ce piège la première fois :
// Mauvaise approche
export default async function Page({ params }) {
const data = await fetchData(params.id)
return (
<div>
{!data ? notFound() : <Content data={data} />} // Déjà dans le JSX !
</div>
)
}
La page 404 s’est affichée, mais le code d’état était 200. Les moteurs de recherche l’ont indexée comme une page normale – le référencement est ruiné.
Approche correcte :
export default async function Page({ params }) {
const data = await fetchData(params.id)
if (!data) {
notFound() // Vérifier d'abord, appeler tout de suite
}
return <Content data={data} /> // Ne retourner du JSX que si les données existent
}
Validez les données, appelez notFound() immédiatement en cas de problème, puis renvoyez JSX. Cela garantit que le code d’état est correctement 404.
not-found.tsx : Création de pages 404 personnalisées
Théorie couverte – passons à la pratique. Nous commencerons par une page 404 de base, puis ajouterons des fonctionnalités étape par étape.
Version de base : Fonctionnalité minimale
Le « not-found.tsx » le plus simple ressemble à ceci :
// app/not-found.tsx
import Link from 'next/link'
export default function NotFound() {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="text-center">
<h1 className="text-6xl font-bold text-gray-900 mb-4">404</h1>
<p className="text-xl text-gray-600 mb-8">
Désolé, la page que vous recherchez n'existe pas
</p>
<Link
href="/"
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
Retour à l'accueil
</Link>
</div>
</div>
)
}
Enregistrez le fichier, visitez un chemin inexistant comme « http://localhost:3000/nonexistent-page » et vous le verrez en action.
Mieux que le noir sur blanc par défaut, non ? Mais c’est quand même trop simple. Les utilisateurs arrivent ici avec un seul bouton « Retour à l’accueil » : que se passe-t-il s’ils recherchent un contenu spécifique ?
Version avancée : plus d’options pour les utilisateurs
Une bonne page 404 doit fournir plusieurs « voies de sortie ». J’ajoute habituellement :
- Champ de recherche : permettez aux utilisateurs de trouver ce dont ils ont besoin
- Liens populaires – Guidez les utilisateurs vers du contenu d’actualité
- Éléments de marque - Logo, couleurs de la marque pour plus de cohérence
Code complet :
// app/not-found.tsx
'use client'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { useState } from 'react'
export default function NotFound() {
const router = useRouter()
const [searchQuery, setRechercherQuery] = useState('')
const handleRechercher = (e: React.FormEvent) => {
e.preventDefault()
if (searchQuery.trim()) {
router.push(`/search?q=${encodeURIComponent(searchQuery)}`)
}
}
const popularLinks = [
{ href: '/blog', label: 'Blog technique' },
{ href: '/projects', label: 'Projets' },
{ href: '/about', label: 'À propos' },
]
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100">
<div className="max-w-2xl w-full px-6 py-12 text-center">
{/* Large 404 */}
<h1 className="text-9xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-blue-600 to-indigo-600 mb-4">
404
</h1>
{/* Friendly message */}
<p className="text-2xl font-medium text-gray-800 mb-2">
Oups, cette page s'est égarée
</p>
<p className="text-gray-600 mb-8">
Ce lien est peut-être obsolète ou la page a été déplacée.<br/>
Pas d'inquiétude — essayez l'une de ces options pour continuer :
</p>
{/* Rechercher box */}
<form onSubmit={handleRechercher} className="mb-8">
<div className="flex gap-2 max-w-md mx-auto">
<input
type="text"
value={searchQuery}
onChange={(e) => setRechercherQuery(e.target.value)}
placeholder="Rechercher ce dont vous avez besoin..."
className="flex-1 px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
<button
type="submit"
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium"
>
Rechercher
</button>
</div>
</form>
{/* Popular links */}
<div className="mb-8">
<p className="text-sm text-gray-600 mb-4">Ou visitez ces pages populaires :</p>
<div className="flex flex-wrap justify-center gap-3">
{popularLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="px-5 py-2 bg-white text-gray-700 rounded-lg border border-gray-200 hover:border-blue-500 hover:text-blue-600 transition-colors"
>
{link.label}
</Link>
))}
</div>
</div>
{/* Back to home */}
<Link
href="/"
className="inline-flex items-center gap-2 text-blue-600 hover:text-blue-700 font-medium"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
Retour à l'accueil
</Link>
</div>
</div>
)
}
Notez le « utiliser le client » en haut. Pourquoi? Le champ de recherche a besoin de « useState » et « useRouter » – des fonctionnalités côté client nécessitant une déclaration de composant client.
Cette version est bien meilleure. Lorsque les utilisateurs voient la page 404 :
- Ils peuvent rechercher directement ce dont ils ont besoin
- Ils peuvent cliquer sur des liens populaires pour parcourir
- En dernier recours, ils peuvent rentrer chez eux
Les taux de rebond diminuent considérablement.
Technique avancée : suivi des erreurs 404
Vous voulez savoir quelles pages inexistantes les utilisateurs visitent (vous devriez peut-être en créer quelques-unes) ? Ajouter des analyses :
'use client'
import { useEffect } from 'react'
import { usePathname } from 'next/navigation'
export default function NotFound() {
const pathname = usePathname()
useEffect(() => {
// Send to your analytics tool
if (typeof window !== 'undefined') {
// Google Analytics example
window.gtag?.('event', 'page_not_found', {
page_path: pathname,
})
// Or send to your own server
fetch('/api/analytics/404', {
method: 'POST',
body: JSON.stringify({ path: pathname }),
}).catch(() => {}) // Failures are OK, don't affect UX
}
}, [pathname])
return (
// ...your 404 UI
)
}
Après avoir collecté des données, vous découvrirez peut-être :
- De nombreux utilisateurs recherchent une ancienne page supprimée → Envisagez une redirection 301
- Haute fréquence de fautes de frappe d’URL spécifiques → Ajouter une correction automatique
- Les utilisateurs recherchent constamment certains contenus → Il est temps de le créer
error.tsx & global-error.tsx : gestion de 500 erreurs
not-found.tsx gère uniquement “la page n’existe pas”. Qu’en est-il des plantages de code, des échecs d’API, des déconnexions de bases de données ? C’est là qu’intervient « error.tsx ».
Utilisation de base d’erreur.tsx
error.tsx doit être un composant client — la première ligne est “use client’`.
Pourquoi réservé aux clients ? Les limites d’erreur React ne peuvent s’exécuter que sur le client. Pas le choix là-bas.
// app/error.tsx
'use client'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="max-w-md w-full px-6 py-8 bg-white rounded-lg shadow-lg">
<div className="text-center">
<div className="text-6xl mb-4">⚠️</div>
<h2 className="text-2xl font-bold text-gray-900 mb-2">Une erreur s'est produite !</h2>
<p className="text-gray-600 mb-6">
Désolé, un problème est survenu lors du chargement de cette page
</p>
<button
onClick={() => reset()}
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium"
>
Réessayer
</button>
<Link
href="/"
className="block mt-4 text-sm text-gray-500 hover:text-gray-700"
>
Retour à l'accueil
</Link>
</div>
</div>
</div>
)
}
Paramètres clés :
- erreur - L’objet d’erreur intercepté contient
messageetdigest(hachage d’erreur) - reset - Une fonction qui restitue ce segment de route pour tenter une récupération
Cliquer sur « Réessayer » appelle « reset() » pour réexécuter le composant défaillant. S’il s’agissait d’un problème de réseau, une nouvelle tentative pourrait fonctionner.
Gestion des erreurs dans l’environnement de production
Il y a un problème de sécurité ici. En développement, « error.message » affiche des détails complets tels que « Échec de la connexion à la base de données : informations d’identification non valides ».
La production ne peut pas faire ça ! Ces informations pourraient divulguer des données sensibles.
Next.js nettoie automatiquement en production, avec des objets « erreur » contenant uniquement :
message- Message d’erreur générique (pas de détails)digest- Hachage d’erreur (pour la correspondance des journaux)
Les détails réels de l’erreur sont consignés dans les journaux du serveur. Utilisez digest pour rechercher les journaux du serveur :
'use client'
export default function Error({ error }: { error: Error & { digest?: string } }) {
return (
<div>
<h2>Une erreur s'est produite</h2>
<p>{error.message}</p>
{error.digest && (
<p className="text-xs text-gray-400 mt-4">
ID d'erreur : {error.digest}
</p>
)}
</div>
)
}
L’utilisateur voit “ID d’erreur : abc123”, vous le capture d’écran, vous prenez cet ID et recherchez dans les journaux du serveur la trace complète de la pile.
Journalisation des erreurs dans les services de surveillance
Je ne peux pas attendre les rapports des utilisateurs en cas d’interruption de la production. Connectez-vous de manière proactive à Sentry, Datadog ou à des services similaires.
'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(() => {
// Send error to Sentry
Sentry.captureException(error)
}, [error])
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<h2>Une erreur s'est produite !</h2>
<button onClick={() => reset()}>Réessayer</button>
</div>
</div>
)
}
useEffect se déclenche une fois en cas d’erreur, envoyant des informations complètes sur l’erreur à Sentry. Dans votre tableau de bord Sentry, vous verrez :
- Trace de pile d’erreurs
- Informations sur le navigateur de l’utilisateur
- Itinéraire où l’erreur s’est produite
- Horodatage
Des problèmes de production ? Vous le saurez dans les 5 minutes au lieu d’attendre des plaintes.
global-error.tsx : la solution de repli ultime
error.tsx est puissant mais a un angle mort : il ne peut pas détecter les erreurs de son propre layout.tsx.
C’est là qu’intervient « global-error.tsx », encapsulant l’intégralité de l’application pour détecter même les erreurs de disposition racine.
// app/global-error.tsx
'use client'
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<html>
<body>
<div style={{ padding: '50px', textAlign: 'center' }}>
<h2>Le site a rencontré une erreur critique</h2>
<p>Nous travaillons à la corriger, réessayez plus tard</p>
<button onClick={() => reset()}>Réessayer</button>
</div>
</body>
</html>
)
}
Trois points clés :
-
Doit inclure les balises
<html>et<body>
Depuis que la disposition racine s’est écrasée, « global-error.tsx » la remplace complètement. Vous devez fournir une structure HTML complète. -
Impossible d’importer des modules CSS ou des styles globaux
Next.js ignore les importations CSS dansglobal-error.tsx. Seuls les styles en ligne ou les balises<style>fonctionnent. -
Faible probabilité de déclenchement
Les dispositions racine sont généralement simples et peu susceptibles d’échouer. « global-error.tsx » ressemble plus à une assurance – rarement déclenchée.
Malgré cela, je recommande toujours de le créer. Mieux qu’un écran blanc s’il se déclenche.
Exemple global-error.tsx complet
Ajoutez un peu de style pour le rendre moins laid :
// app/global-error.tsx
'use client'
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<html>
<body>
<style>{`
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.container {
text-align: center;
color: white;
padding: 2rem;
}
h2 {
font-size: 2.5rem;
margin-bottom: 1rem;
}
p {
font-size: 1.2rem;
margin-bottom: 2rem;
opacity: 0.9;
}
button {
padding: 12px 32px;
font-size: 1rem;
background: white;
color: #667eea;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
`}</style>
<div className="container">
<h2>😵 Erreur système critique</h2>
<p>Nous sommes désolés, le site a rencontré un problème inattendu<br/>Notre équipe a été alertée et intervient en urgence</p>
<button onClick={() => reset()}>Recharger</button>
<p style={{ fontSize: '0.875rem', marginTop: '2rem', opacity: 0.7 }}>
ID d'erreur : {error.digest || 'unknown'}
</p>
</div>
</body>
</html>
)
}
Je ne peux pas utiliser Tailwind, je ne peux pas importer de fichiers CSS, je dois écrire des styles dans les balises <style>. Primitif, mais ça marche.
Meilleures pratiques de conception de pages d’erreur : maintenir l’engagement des utilisateurs
Code terminé – mais ne terminez pas encore. La mise en œuvre technique n’est que la première étape. Le design détermine véritablement si les utilisateurs restent ou partent.
J’ai étudié 404 pages de Spotify, Figma, Mailchimp et d’autres sociétés similaires, trouvant plusieurs points communs.
Éléments essentiels : donner aux utilisateurs des itinéraires de sortie
Une page d’erreur décente a besoin d’au moins :
1. Messages d’erreur clairs mais non menaçants
❌ N’écrivez pas ceci :```
Error 404: The requested resource could not be located on the server.
Qui comprend ça ? Les utilisateurs pensent : « C'est quoi ce charabia : le site est-il en panne ? »
✅Écrivez plutôt ceci :```
Oups, cette page s'est égarée
This link might be outdated or the page has moved
Utilisez un langage simple et non un jargon technique qui effraie les utilisateurs.
2. Navigation principale ou lien d’accueil
La « voie de sortie » de base. Les utilisateurs savent au moins qu’ils peuvent se remettre en sécurité.
<Link href="/" className="text-blue-600">Retour à l'accueil</Link>
3. Champ de recherche
Les utilisateurs peuvent avoir des fautes de frappe dans l’URL ou cliquer sur un lien expiré. Donnez-leur un champ de recherche pour trouver ce dont ils ont besoin.
La page 404 de Spotify comporte un grand champ de recherche avec le texte « Recherchez ce que vous recherchez ». Simple et direct.
4. Contenu recommandé ou pages populaires
Puisque les utilisateurs sont ici, donnez-leur quelque chose à voir :
- Sites de blog → Recommander les articles récents
- E-commerce → Recommander des produits populaires
- Produits SaaS → Afficher les entrées de fonctionnalités principales
La page 404 de Netflix recommande des émissions populaires : de nombreux utilisateurs cliquent dessus et commencent à regarder, oubliant ce qu’ils voulaient à l’origine.
5. Maintenir la cohérence de la marque
Logo, couleurs, polices : tout doit correspondre au reste de votre site.
Les pages d’erreur font partie de l’expérience de la marque. Les utilisateurs qui voient une page blanche sans conception se demandent : « Ce site est-il vraiment fiable ? »
Stratégies de conception : diffuser la maladresse
Au-delà de la fonctionnalité, l’ambiance compte.
Utilisez l’humour pour apaiser les tensions
La page 404 de Figma comporte une petite animation : un composant d’interface utilisateur qui s’exécute sur l’écran, impossible à cliquer. Texte : “Hmm, nous ne trouvons pas cette page.”
Léger et humoristique, les utilisateurs ne pensent pas « Oh non, le site est en panne » mais sourient plutôt.
Mais n’en faites pas trop. Les entreprises technologiques peuvent jouer avec l’humour, les sites financiers/médicaux ne devraient pas le faire – les utilisateurs trouvent cela peu professionnel.
Offrir une compensation (pour le commerce électronique)
Certains sites e-commerce mettent des petits coupons sur les pages 404 : “Page perdue, voici un code de réduction de 10% pour compenser”.
Les utilisateurs initialement déçus se contentent de la réduction, se rendent au magasin, voire commandent.
N’oubliez pas l’optimisation mobile
40 % du trafic provient du mobile, les pages d’erreur doivent également être adaptées.
- Les boutons doivent être suffisamment grands pour les doigts (minimum 44x44px)
- Moins de texte – petits écrans de téléphone
- Liens les plus importants en haut, immédiatement visibles
J’ai vu une page 404 magnifiquement conçue sur un ordinateur mais avec de minuscules boutons sur un mobile – il m’a fallu trois clics pour appuyer sur « Retour à l’accueil ». UX ruiné.
Exemples réels : le bien contre le mal
Mauvais exemple - Un site gouvernemental :
- Noir sur blanc, “Erreur 404 introuvable”
- Aucun lien du tout
- Pas de champ de recherche
- Pas de logo
Les utilisateurs le voient, 100 % de rebond.
Bon exemple - Airbnb :
- Gros titre : “Nous n’arrivons pas à trouver la page que vous recherchez”
- Champ de recherche : “Essayez de rechercher des hôtels à Paris”
- Liens recommandés : Maisons, Expériences, Expériences en ligne
- Maintient les couleurs et les polices de la marque Airbnb
Même si les utilisateurs ne trouvent pas leur page cible, ils sont attirés par les recommandations et continuent de naviguer.
Les données parlent
J’ai fait un test A/B sur mon blog :
Version A (404 par défaut) :
- Taux de rebond : 78%
- Temps moyen : 3 secondes
Version B (404 personnalisé avec champ de recherche et publications recommandées) :
- Taux de rebond : 42%
- Temps moyen : 35 secondes
Taux de rebond réduit de moitié ! 20 % des utilisateurs ont cliqué sur les articles recommandés et ont continué à lire.
C’est le pouvoir du design. Même “la page n’existe pas” : l’une perd des utilisateurs, l’autre les garde.
Problèmes et pièges courants
J’ai travaillé sur de nombreux projets et je suis tombé sur de nombreux pièges. Voici les problèmes les plus fréquents avec des solutions pour vous aider à les éviter.
Problème 1 : notFound() renvoie 200 au lieu de 404
Symptômes:
Appelée « notFound() », la page 404 s’affiche correctement, mais les outils de développement du navigateur affichent le statut HTTP 200. Google les indexe comme des pages normales, le référencement étant complètement cassé.
Cause:
Le streaming a démarré, le statut HTTP est verrouillé à 200. Une fois que JSX revient, il est trop tard.
Solution:
Appelez notFound() avant de renvoyer un JSX.
// ❌ Wrong: Already inside JSX
export default async function Page({ params }) {
const data = await fetchData(params.id)
return <div>{!data ? notFound() : <Content data={data} />}</div>
}
// ✅ Correct: Validate first, then return
export default async function Page({ params }) {
const data = await fetchData(params.id)
if (!data) {
notFound() // Call immediately, don't wait
}
return <Content data={data} />
}
N’oubliez pas : Validez d’abord, appelez d’abord, effectuez le rendu en dernier.
Problème 2 : Les styles ne fonctionnent pas dans global-error.tsx
Symptômes:
Modules Tailwind CSS ou CSS importés dans global-error.tsx, mais la page n’affiche aucun style.
Cause:
Next.js ignore toutes les importations CSS dans global-error.tsx. Limite connue.
Solution:
Utilisez uniquement des styles en ligne ou des balises <style>.
// ❌ Wrong: Imports don't work
import './styles.css' // Won't apply
export default function GlobalError() {
return <div className="bg-blue-500">Error</div> // Tailwind won't work either
}
// ✅ Correct: Use <style> tags
export default function GlobalError() {
return (
<html>
<body>
<style>{`
.error-container {
background: #3b82f6;
color: white;
padding: 2rem;
}
`}</style>
<div className="error-container">Error</div>
</body>
</html>
)
}
Primitif, mais ça marche. J’extrais généralement les styles dans une constante de chaîne pour un code plus propre.
Problème 3 : La route imbriquée not-found.tsx ne fonctionne pas
Symptômes:
Création d’un 404 personnalisé dans app/blog/[slug]/not-found.tsx, mais la visite de /blog/nonexistent-article affiche toujours la racine 404.
Cause:
Généralement deux problèmes :
- Mauvais emplacement du fichier
- Je n’ai pas appelé
notFound()danspage.tsx
Solution:
Confirmez la structure du fichier :
app/
├── not-found.tsx ← Global 404
└── blog/
└── [slug]/
├── page.tsx ← Must call notFound() here
└── not-found.tsx ← 404 dédié au blog
Ensuite, appelez activement page.tsx :
// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation'
export default async function BlogPost({ params }) {
const post = await getPost(params.slug)
if (!post) {
notFound() // Déclenche le not-found.tsx du même niveau
}
return <article>{post.title}</article>
}
La visite d’une route complètement inexistante (comme /asdfghjkl) déclenche la racine app/not-found.tsx.
La route imbriquée not-found.tsx ne se déclenche que lorsque la page.tsx correspondante appelle activement notFound().
Problème 4 : error.tsx ne détecte pas certaines erreurs
Symptômes:
La connexion à la base de données a échoué, mais « error.tsx » ne s’est pas déclenché — la page est devenue vide ou a affiché la page d’erreur racine.
Cause:
error.tsx détecte uniquement les erreurs des routes sœurs et enfants. Si l’erreur se produit dans son propre « layout.tsx », il ne peut pas la détecter.
De plus, notFound() ignore error.tsx et déclenche directement not-found.tsx.
Solution:
Si vous soupçonnez des problèmes de mise en page, ajoutez « error.tsx » à la route parent ou à la racine :
app/
├── error.tsx ← Can catch root layout child component errors
├── global-error.tsx ← Can catch root layout itself errors
└── dashboard/
├── layout.tsx ← Errors here can't be caught below
└── error.tsx ← Only catches page.tsx and child route errors
Si vous avez vraiment besoin de détecter les erreurs de mise en page, utilisez « global-error.tsx ».
Problème 5 : Impossible de voir les messages d’erreur en production
Symptômes:
Le développement affiche des messages d’erreur détaillés, la production « error.message » affiche uniquement « Erreur d’application ».
Cause:
Mécanisme de sécurité Next.js empêchant les fuites d’informations sensibles.
Solution:
Utilisez error.digest pour rechercher des informations complètes dans les journaux du serveur :
'use client'
export default function Error({ error }) {
return (
<div>
<p>Une erreur s'est produite: {error.message}</p>
<p className="text-xs text-gray-400">
ID d'erreur : {error.digest} {/* À montrer à l'utilisateur */}
</p>
</div>
)
}
L’utilisateur vous le fait une capture d’écran, vous prenez ce « résumé » et recherchez les journaux du serveur (Vercel, Sentry, Datadog) pour la trace complète de la pile.
Ou utilisez directement « useEffect » dans « error.tsx » pour envoyer des erreurs aux services de surveillance — pas d’attente pour les rapports des utilisateurs.
Conclusion
Récapitulatif rapide — La gestion des erreurs Next.js comporte trois niveaux :
- not-found.tsx → la page 404 n’existe pas
- error.tsx → Erreurs d’exécution
- global-error.tsx → Mise en page racine de secours
La mise en œuvre technique n’est pas difficile. Le véritable défi est le design. Une bonne page d’erreur peut réduire les taux de rebond de 78 % à 42 %. Je n’invente pas cela, ce sont mes propres données de test.
Offrez aux utilisateurs un champ de recherche, des liens recommandés et un message convivial. C’est aussi simple que cela.
Allez maintenant vérifier votre projet Next.js – vous utilisez toujours les pages d’erreur par défaut ? Passez une demi-heure à le réparer. Vos utilisateurs vous remercieront.
N’hésitez pas à commenter si vous rencontrez des problèmes, j’essaierai de répondre. Si cet article vous a aidé, partagez-le avec quelqu’un qui en a besoin.
Créer une page 404 Next.js personnalisée
Guide pas à pas pour créer une page 404 personnalisée dans l'App Router Next.js, avec champ de recherche et liens recommandés
- 1
Step 1: Créer le fichier not-found.tsx
Créez not-found.tsx dans le dossier app comme page 404 globale - 2
Step 2: Ajouter les composants UI de base
Importez le composant Link de Next.js et créez une interface avec message d'erreur et bouton retour à l'accueil - 3
Step 3: Ajouter la directive use client
Si vous utilisez la gestion d'état ou des interactions (comme un champ de recherche), ajoutez use client en tête de fichier - 4
Step 4: Implémenter la recherche
Utilisez useState pour la saisie et useRouter pour la navigation vers les résultats - 5
Step 5: Ajouter des liens populaires
Créez un tableau de liens recommandés et affichez-les avec le composant Link - 6
Step 6: Appliquer le style
Utilisez Tailwind CSS ou une autre solution de style pour harmoniser la page avec votre marque - 7
Step 7: Déclencher le 404 dans les pages
Dans page.tsx des routes dynamiques, appelez notFound() lorsque les données n'existent pas - 8
Step 8: Tester et valider
Visitez un chemin inexistant et vérifiez dans les outils développeur que le code HTTP est 404
FAQ
Quelle est la différence entre not-found.tsx, error.tsx et global-error.tsx dans Next.js ?
Pourquoi notFound() renvoie-t-il encore le code HTTP 200 au lieu de 404 ?
Pourquoi global-error.tsx ne peut-il pas utiliser Tailwind CSS ou importer des fichiers CSS ?
Comment suivre les pages inexistantes visitées par les utilisateurs ?
Quels éléments doit inclure une page 404 personnalisée pour réduire le taux de rebond ?
11 min de lecture · Publié le: 5 janv. 2026 · Mis à jour le: 27 juil. 2026
Guide complet Next.js
Si vous arrivez depuis la recherche, le plus rapide est de passer à l’article précédent ou suivant de cette série.
Précédent
Gestion des états de chargement Next.js : guide pratique de loading.tsx et Suspense
Maîtrisez loading.tsx et Suspense dans Next.js, dites adieu au useState manuel et obtenez une expérience de chargement professionnelle avec un minimum de code. Skeleton screens, routes dynamiques et solutions aux problèmes courants.
Partie 32 sur 51
Suivant
Guide complet Next.js Error Boundary : 5 techniques pour gérer élégamment les erreurs runtime
Maîtrisez Error Boundary dans Next.js : error.tsx, gestion globale, cas particuliers des Server Components et mécanismes de récupération — évitez l'écran blanc et améliorez l'expérience utilisateur.
Partie 34 sur 51



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire