Next.js 404- und 500-Seiten anpassen: Vom technischen Setup bis zum Design

Freitagnachmittag, 15 Uhr – der Product Manager postet plötzlich einen Screenshot in den Chat: „Ist das unsere Website? Sieht ja schrecklich aus!“
Ich klicke drauf – weißer Hintergrund, schwarzer Text, das nackte „404 This page could not be found“. Peinlich.
Erst als die Zahlen kamen, wurde es ernst: 40 % der Nutzer schlossen den Tab, nachdem sie die Standard-404-Seite gesehen hatten.
Bei Next.js-Projekten konzentrieren wir uns meist auf die „normalen“ Seiten: die Startseite soll schön sein, Listen flüssig, Detailseiten perfekt. Fehlerseiten? Wer kümmert sich schon darum – die sieht man selten.
Diese Zahl hat mich wachgerüttelt: Fehlerseiten sind kein optionales Extra, sondern Ihre letzte Chance, Nutzer zu halten. Stellen Sie sich vor, jemand klickt auf einen toten Link, will eigentlich noch auf Ihrer Seite stöbern – und sieht dann eine langweilige weiße Seite mit dem kalten Hinweis „Seite nicht gefunden“. Keine Navigation, kein Suchfeld, kein Hinweis. Was denkt der Nutzer? „Ist diese Website überhaupt seriös?“
Zum Glück bietet der Next.js App Router ein vollständiges Fehlerbehandlungssystem: not-found.tsx für 404, error.tsx für Laufzeitfehler, global-error.tsx als Fallback für die gesamte App. Klingt einfach? Die Fallstricke sind zahlreich.
Beim ersten Setup lieferte der HTTP-Status hartnäckig 200 statt 404 – Google indexierte meine 404-Seiten gar nicht mehr. Ein anderes Mal wollten die Styles in global-error.tsx einfach nicht greifen; erst nach stundenlangem Dokumentationslesen wurde klar: CSS-Modul-Imports werden dort nicht unterstützt.
In diesem Artikel führe ich Sie Schritt für Schritt durch die Next.js-Fehlerseiten – von der Grundnutzung von not-found.tsx über Error Boundaries in error.tsx bis hin zum Design einer 404-Seite, die Nutzer wirklich hält. Der Code ist vollständig, die Fallen habe ich selbst ausprobiert – Sie können direkt loslegen.
Next.js-Fehlerbehandlung im Überblick
Als ich den App Router das erste Mal nutzte, war mir lange unklar, was die drei Dateien eigentlich unterscheidet. not-found.tsx, error.tsx, global-error.tsx – die Namen klingen ähnlich, die Aufgaben sind völlig verschieden.
Die drei Fehlerdateien im Überblick
Kurz gesagt:
- not-found.tsx – speziell für 404, wenn eine Seite nicht existiert
- error.tsx – für Laufzeitfehler, z. B. fehlgeschlagene Datenladung oder Codefehler
- global-error.tsx – letztes Sicherheitsnetz, wenn sogar das Root-Layout ausfällt
Warum drei Dateien? Reicht nicht eine error.tsx?
Next.js behandelt Fehler hierarchisch – wie Matroschka-Puppen. error.tsx fängt nur Fehler auf derselben Ebene und in Unterrouten ab, nicht Fehler im eigenen layout.tsx. Was, wenn das Root-Layout selbst scheitert? Dafür brauchen Sie global-error.tsx.
not-found.tsx ist etwas Besonderes – es hat sogar höhere Priorität als error.tsx. Wenn Sie aktiv notFound() aufrufen, überspringt Next.js error.tsx und rendert direkt not-found.tsx.
Die Dateiposition ist entscheidend
Alle drei Dateien können auf verschiedenen Routing-Ebenen liegen – die Position bestimmt den Gültigkeitsbereich.
Fehlerdateien auf Root-Ebene (im app/-Verzeichnis):
app/
├── layout.tsx
├── not-found.tsx ← Globale 404-Seite
├── error.tsx ← Globales Error Handling
├── global-error.tsx ← Fallback für Root-Layout
└── page.tsx
Fehlerdateien auf Route-Ebene (unter einer bestimmten Route):
app/
├── blog/
│ ├── [slug]/
│ │ ├── page.tsx
│ │ ├── not-found.tsx ← Blog-spezifische 404
│ │ └── error.tsx ← Blog-spezifische Fehlerseite
Ruft ein Nutzer /blog/nicht-existierender-artikel auf, zeigt Next.js zuerst app/blog/[slug]/not-found.tsx – nicht die globale app/not-found.tsx. So können Sie pro Modul unterschiedliche Fehlerseiten gestalten.
notFound()-Funktion: 404 programmatisch auslösen
Eine not-found.tsx-Datei allein reicht nicht – Sie müssen wissen, wann sie ausgelöst wird.
Typisches Szenario: Daten anhand einer ID laden, aber die Daten existieren nicht.
// 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() // not-found.tsx auslösen
}
return <article>{post.title}</article>
}
Achtung, ein häufiger Fehler: notFound() muss aufgerufen werden, bevor irgendein JSX zurückgegeben wird. Wenn Sie bereits Teilinhalte zurückgeben, hat das Streaming begonnen – der HTTP-Status bleibt dann bei 200 statt 404.
Genau das ist mir passiert:
// Falsches Beispiel
export default async function Page({ params }) {
const data = await fetchData(params.id)
return (
<div>
{!data ? notFound() : <Content data={data} />} // JSX läuft schon!
</div>
)
}
Die 404-Seite erschien zwar, aber der HTTP-Status war 200 – Suchmaschinen indexierten das als normale Seite. SEO kaputt.
Richtige Variante:
export default async function Page({ params }) {
const data = await fetchData(params.id)
if (!data) {
notFound() // Erst prüfen, dann aufrufen
}
return <Content data={data} /> // JSX nur bei vorhandenen Daten
}
Erst Daten validieren, bei Fehler sofort notFound(), dann erst JSX zurückgeben. So ist der Status korrekt 404.
not-found.tsx: Benutzerdefinierte 404-Seite in der Praxis
Theorie genug – jetzt wird gebaut. Wir starten mit einer Basisversion und erweitern sie Schritt für Schritt.
Basisversion: Erst mal funktionsfähig
Die einfachste not-found.tsx sieht so aus:
// 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">
Entschuldigung, die angeforderte Seite existiert nicht
</p>
<Link
href="/"
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
Zur Startseite
</Link>
</div>
</div>
)
}
Speichern, einen nicht existierenden Pfad aufrufen – z. B. http://localhost:3000/nicht-existierende-seite – und schon sieht es besser aus als das Standard-Weiß-Schwarz.
Aber noch zu simpel: Ein einziger „Zur Startseite“-Button reicht nicht, wenn der Nutzer etwas Bestimmtes sucht.
Erweiterte Version: Mehr Auswege für Nutzer
Eine gute 404-Seite bietet mehrere Wege weiter:
- Suchfeld – Nutzer finden selbst, was sie suchen
- Beliebte Links – Hinweis auf populäre Inhalte
- Markenelemente – Logo, Markenfarben, konsistentes Erscheinungsbild
Der vollständige Code:
// 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, setSearchQuery] = useState('')
const handleSearch = (e: React.FormEvent) => {
e.preventDefault()
if (searchQuery.trim()) {
router.push(`/search?q=${encodeURIComponent(searchQuery)}`)
}
}
const popularLinks = [
{ href: '/blog', label: 'Tech-Blog' },
{ href: '/projects', label: 'Projekte' },
{ href: '/about', label: 'Über uns' },
]
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">
{/* Große 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>
{/* Freundliche Fehlermeldung */}
<p className="text-2xl font-medium text-gray-800 mb-2">
Hoppla, diese Seite ist verschwunden
</p>
<p className="text-gray-600 mb-8">
Der Link ist vielleicht abgelaufen oder die Seite wurde verschoben.<br/>
Keine Sorge – probieren Sie eine der folgenden Optionen:
</p>
{/* Suchfeld */}
<form onSubmit={handleSearch} className="mb-8">
<div className="flex gap-2 max-w-md mx-auto">
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Suchen Sie nach Inhalten..."
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"
>
Suchen
</button>
</div>
</form>
{/* Beliebte Links */}
<div className="mb-8">
<p className="text-sm text-gray-600 mb-4">Oder besuchen Sie diese beliebten Seiten:</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>
{/* Zur Startseite */}
<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>
Zur Startseite
</Link>
</div>
</div>
)
}
Am Dateianfang steht 'use client'. Warum? Suchfeld braucht useState und useRouter – das sind Client-Funktionen, die Komponente muss als Client Component deklariert werden.
Diese Version ist deutlich besser. Nutzer können:
- direkt suchen
- beliebte Links anklicken
- im Notfall zur Startseite zurück
Die Absprungrate sinkt spürbar.
Fortgeschritten: 404-Fehler tracken
Wenn Sie wissen wollen, welche nicht existierenden Seiten Nutzer aufrufen (vielleicht fehlen Inhalte, die Sie anlegen sollten), fügen Sie Tracking hinzu:
'use client'
import { useEffect } from 'react'
import { usePathname } from 'next/navigation'
export default function NotFound() {
const pathname = usePathname()
useEffect(() => {
// An Ihr Analytics-Tool senden
if (typeof window !== 'undefined') {
// Google Analytics Beispiel
window.gtag?.('event', 'page_not_found', {
page_path: pathname,
})
// Oder an Ihren eigenen Server
fetch('/api/analytics/404', {
method: 'POST',
body: JSON.stringify({ path: pathname }),
}).catch(() => {}) // Fehler ignorieren – UX nicht beeinträchtigen
}
}, [pathname])
return (
// ... Ihre 404-UI
)
}
Nach einiger Zeit sehen Sie vielleicht:
- Viele Nutzer suchen eine gelöschte alte Seite → 301-Weiterleitung einrichten
- Ein URL-Tippfehler tritt sehr häufig auf → automatische Korrektur
- Bestimmte Inhalte werden oft gesucht → fehlende Inhalte ergänzen
error.tsx und global-error.tsx: 500-Fehler behandeln
not-found.tsx deckt nur „Seite nicht gefunden“ ab. Was ist mit Codefehlern, ausgefallenen APIs oder Datenbankproblemen? Dafür kommt error.tsx ins Spiel.
Grundlagen von error.tsx
error.tsx muss eine Client Component sein – die erste Zeile ist 'use client'.
Warum zwingend Client? React Error Boundaries laufen nur im Browser, Server-seitig geht das nicht.
// app/error.tsx
'use client'
import Link from 'next/link'
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">Etwas ist schiefgelaufen!</h2>
<p className="text-gray-600 mb-6">
Beim Laden der Seite ist ein Fehler aufgetreten
</p>
<button
onClick={() => reset()}
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium"
>
Erneut versuchen
</button>
<Link
href="/"
className="block mt-4 text-sm text-gray-500 hover:text-gray-700"
>
Zur Startseite
</Link>
</div>
</div>
</div>
)
}
Zwei wichtige Parameter:
- error – das erfasste Fehlerobjekt mit
messageunddigest(Fehler-Hash) - reset – Funktion, die das Route-Segment neu rendert und einen Wiederherstellungsversuch startet
Ein Klick auf „Erneut versuchen“ führt reset() aus. Bei vorübergehenden Netzwerkfehlern kann das ausreichen.
Fehlerinformationen in der Produktion
Sicherheitsaspekt: In der Entwicklung zeigt error.message den vollständigen Fehler – z. B. „Database connection failed: invalid credentials“.
In der Produktion darf das nicht passieren – sensible Daten könnten durchsickern.
Next.js maskiert automatisch. Das error-Objekt enthält nur noch:
message– allgemeine Fehlermeldung ohne Detailsdigest– Fehler-Hash zum Abgleich mit Server-Logs
Die echten Details landen in den Server-Logs. Mit digest finden Sie sie:
'use client'
export default function Error({ error }: { error: Error & { digest?: string } }) {
return (
<div>
<h2>Etwas ist schiefgelaufen</h2>
<p>{error.message}</p>
{error.digest && (
<p className="text-xs text-gray-400 mt-4">
Fehler-ID: {error.digest}
</p>
)}
</div>
)
}
Der Nutzer sieht „Fehler-ID: abc123“, schickt Ihnen einen Screenshot – und Sie finden den vollständigen Stack in den Logs.
Fehler an Monitoring-Dienste senden
Online-Fehler sollten nicht auf Nutzer-Feedback warten. Besser: automatisch an Sentry, Datadog oder ähnliche Dienste melden.
'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(() => {
// Fehler an Sentry senden
Sentry.captureException(error)
}, [error])
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<h2>Etwas ist schiefgelaufen!</h2>
<button onClick={() => reset()}>Erneut versuchen</button>
</div>
</div>
)
}
useEffect feuert einmal bei Fehler und sendet die vollständigen Infos an Sentry. Im Dashboard sehen Sie:
- Stack Trace
- Browser-Informationen
- Route zum Fehlerzeitpunkt
- Zeitstempel
Probleme erkennen Sie in Minuten – nicht erst bei Beschwerden.
global-error.tsx: Das letzte Sicherheitsnetz
error.tsx ist mächtig, hat aber eine Lücke: Fehler im eigenen layout.tsx fängt es nicht ab.
Dafür gibt es global-error.tsx – es umschließt die gesamte App und fängt sogar Root-Layout-Fehler ab.
// 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>Die Website ist auf einen schweren Fehler gestoßen</h2>
<p>Wir arbeiten an der Behebung – bitte versuchen Sie es später erneut</p>
<button onClick={() => reset()}>Erneut versuchen</button>
</div>
</body>
</html>
)
}
Drei wichtige Punkte:
-
<html>und<body>sind Pflicht
Wenn das Root-Layout ausfällt, ersetztglobal-error.tsxes vollständig. Sie müssen die HTML-Struktur selbst bereitstellen. -
Keine CSS-Module oder globalen Styles importieren
Next.js ignoriert CSS-Imports inglobal-error.tsx. Nur Inline-Styles oder<style>-Tags. -
Selten ausgelöst
Root-Layouts sind meist simpel – echte Auslöser sind selten.global-error.tsxist eher eine Versicherung.
Trotzdem empfehle ich, sie anzulegen. Im Ernstfall ist das besser als ein weißer Bildschirm.
Vollständiges global-error.tsx-Beispiel
Mit etwas Styling sieht es weniger roh aus:
// 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>😵 Schwerer Systemfehler</h2>
<p>Es tut uns leid – die Website ist auf ein unerwartetes Problem gestoßen<br/>Unser Team wurde benachrichtigt und arbeitet an der Behebung</p>
<button onClick={() => reset()}>Neu laden</button>
<p style={{ fontSize: '0.875rem', marginTop: '2rem', opacity: 0.7 }}>
Fehler-ID: {error.digest || 'unknown'}
</p>
</div>
</body>
</html>
)
}
Kein Tailwind, keine CSS-Datei – Styles nur im <style>-Tag. Etwas altmodisch, aber funktional.
Design-Best-Practices: Nutzer auf Fehlerseiten halten
Code fertig – aber noch nicht abschließen. Technische Umsetzung ist Schritt eins; über Bleiben oder Gehen entscheidet das Design.
Ich habe 404-Seiten von Spotify, Figma und Mailchimp analysiert – gemeinsame Muster:
Pflichtelemente: Auswege bieten
Eine solide Fehlerseite braucht mindestens:
1. Klare, aber nicht einschüchternde Fehlermeldung
❌ Nicht so:
Error 404: The requested resource could not be located on the server.
Wer versteht das? Nutzer denken: „Ist die Website kaputt?“
✅ Besser so:
Hoppla, diese Seite ist verschwunden
Der Link ist vielleicht abgelaufen oder die Seite wurde verschoben
Menschenverständlich – kein Technikjargon.
2. Hauptnavigation oder Link zur Startseite
Der minimale Rettungsweg. Nutzer wissen, wo sie sicher landen.
<Link href="/" className="text-blue-600">Zur Startseite</Link>
3. Suchfeld
Vielleicht war die URL falsch getippt oder der Link abgelaufen. Ein Suchfeld hilft beim Selbstfinden.
Spotify setzt auf ein großes Suchfeld mit dem Text „Search for what you’re looking for“. Klar und direkt.
4. Empfohlene Inhalte oder beliebte Seiten
Der Nutzer ist da – geben Sie ihm etwas zu sehen:
- Blog → neueste Artikel empfehlen
- E-Commerce → beliebte Produkte
- SaaS → Einstieg in Kernfunktionen
Netflix empfiehlt auf der 404-Seite beliebte Serien – viele bleiben und vergessen, warum sie kamen.
5. Markenkonsistenz
Logo, Farben, Schrift – wie auf dem Rest der Website.
Fehlerseiten sind Teil der Markenerfahrung. Eine langweilige weiße Seite wirkt unseriös.
Designstrategien: Peinlichkeit entschärfen
Neben Funktion zählt die Atmosphäre.
Humor entschärft
Figma zeigt auf der 404-Seite eine Animation: ein UI-Element huscht über den Bildschirm, unerreichbar. Text: „Hmm, we can’t find that page.“
Leicht und sympathisch – Nutzer denken nicht „Alles kaputt“, sondern schmunzeln.
Aber nicht übertreiben. Tech-Firmen dürfen spielen; Finanz- oder Medizin-Websites wirken damit unprofessionell.
Entschädigung (E-Commerce)
Manche Shops bieten auf der 404-Seite einen Gutschein: „Seite nicht gefunden – hier 10 % Rabatt.“
Enttäuschung wird Kaufimpuls – manchmal bestellen Nutzer trotzdem.
Mobile nicht vergessen
Etwa 40 % des Traffics kommt vom Handy – Fehlerseiten müssen passen.
- Buttons groß genug (mindestens 44×44 px)
- Wenig Text auf kleinem Display
- Wichtigste Links oben, sofort sichtbar
Ich habe 404-Seiten gesehen, die am Desktop schön sind – am Handy winzige Buttons, dreimal tippen für „Zur Startseite“. UX ruiniert.
Praxisbeispiele: Gut und schlecht
Schlechtes Beispiel – manche Behörden-Websites:
- Weiß-Schwarz, „Error 404 Not Found“
- Keine Links
- Kein Suchfeld
- Kein Logo
100 % Absprung.
Gutes Beispiel – Airbnb:
- Große Überschrift: „We can’t seem to find the page you’re looking for“
- Suchfeld: „Try searching for hotels in Paris“
- Links: Homes, Experiences, Online Experiences
- Airbnb-Farben und -Typografie
Nutzer bleiben oft wegen der Empfehlungen – auch ohne Zielseite.
Zahlen sprechen lassen
A/B-Test auf meinem Blog:
Version A (Standard-404):
- Absprungrate: 78 %
- Durchschnittliche Verweildauer: 3 Sekunden
Version B (benutzerdefinierte 404 mit Suche und Artikelempfehlungen):
- Absprungrate: 42 %
- Durchschnittliche Verweildauer: 35 Sekunden
Absprungrate halbiert! 20 % klickten empfohlene Artikel und lasen weiter.
Gleicher Fehler „Seite nicht existiert“ – ein Design vertreibt, das andere hält.
Häufige Probleme und Erfahrungen aus der Praxis
Viele Projekte, viele Fallen. Hier die häufigsten Probleme und Lösungen.
Problem 1: notFound() liefert 200 statt 404
Symptom:
notFound() wurde aufgerufen, 404-Seite erscheint – aber in den DevTools steht HTTP 200. Google indexiert als normale Seite, SEO leidet.
Ursache:
Streaming hat begonnen, Status ist auf 200 gesperrt. Sobald JSX fließt, ist es zu spät.
Lösung:
notFound() vor jedem JSX-Return aufrufen.
// ❌ Falsch: JSX läuft schon
export default async function Page({ params }) {
const data = await fetchData(params.id)
return <div>{!data ? notFound() : <Content data={data} />}</div>
}
// ✅ Richtig: Erst prüfen, dann rendern
export default async function Page({ params }) {
const data = await fetchData(params.id)
if (!data) {
notFound() // Sofort aufrufen
}
return <Content data={data} />
}
Merksatz: Erst prüfen, dann notFound(), dann rendern.
Problem 2: Styles in global-error.tsx greifen nicht
Symptom:
Tailwind oder CSS-Module in global-error.tsx importiert – auf der Seite nichts sichtbar.
Ursache:
Next.js ignoriert CSS-Imports in global-error.tsx. Bekannte Einschränkung.
Lösung:
Nur Inline-Styles oder <style>-Tags.
// ❌ Falsch: Import wirkt nicht
import './styles.css' // Wird ignoriert
export default function GlobalError() {
return <div className="bg-blue-500">Fehler</div> // Tailwind auch nicht
}
// ✅ Richtig: style-Tag
export default function GlobalError() {
return (
<html>
<body>
<style>{`
.error-container {
background: #3b82f6;
color: white;
padding: 2rem;
}
`}</style>
<div className="error-container">Fehler</div>
</body>
</html>
)
}
Etwas roh, aber funktional. Styles oft als String-Konstante auslagern.
Problem 3: not-found.tsx in verschachtelten Routen greift nicht
Symptom:
app/blog/[slug]/not-found.tsx existiert, aber /blog/nicht-existierender-artikel zeigt die globale 404.
Ursache:
Meist zwei Dinge:
- Falsche Dateiposition
- Kein
notFound()inpage.tsx
Lösung:
Struktur prüfen:
app/
├── not-found.tsx ← Globale 404
└── blog/
└── [slug]/
├── page.tsx ← notFound() hier aufrufen
└── not-found.tsx ← Blog-spezifische 404
In page.tsx aktiv auslösen:
// 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() // Löst not-found.tsx auf derselben Ebene aus
}
return <article>{post.title}</article>
}
Komplett unbekannte Routen wie /asdfghjkl nutzen app/not-found.tsx.
Verschachtelte not-found.tsx greift nur, wenn die zugehörige page.tsx notFound() aufruft.
Problem 4: error.tsx fängt manche Fehler nicht
Symptom:
Datenbankverbindung fehlgeschlagen – error.tsx erscheint nicht, weißer Bildschirm oder Root-Fehlerseite.
Ursache:
error.tsx fängt nur Fehler auf derselben Ebene und darunter. Fehler im eigenen layout.tsx nicht.
Außerdem: notFound() überspringt error.tsx und geht direkt zu not-found.tsx.
Lösung:
Bei Layout-Verdacht error.tsx auf übergeordneter oder Root-Ebene:
app/
├── error.tsx ← Fehler in Root-Layout-Unterkomponenten
├── global-error.tsx ← Fehler im Root-Layout selbst
└── dashboard/
├── layout.tsx ← Fehler hier: unteres error.tsx hilft nicht
└── error.tsx ← Nur page.tsx und Unterrouten
Layout-Fehler wirklich abfangen: global-error.tsx.
Problem 5: In Produktion keine Fehlerdetails
Symptom:
Entwicklung: detaillierte Meldungen. Produktion: error.message nur „Application error“.
Ursache:
Next.js-Sicherheitsmechanismus gegen Informationslecks.
Lösung:
Mit error.digest in Server-Logs nachschlagen:
'use client'
export default function Error({ error }) {
return (
<div>
<p>Fehler: {error.message}</p>
<p className="text-xs text-gray-400">
Fehler-ID: {error.digest} {/* Dem Nutzer anzeigen */}
</p>
</div>
)
}
Screenshot mit ID → in Vercel-, Sentry- oder Datadog-Logs den vollständigen Stack finden.
Oder in error.tsx per useEffect direkt an Monitoring senden – ohne auf Nutzer-Feedback zu warten.
Fazit
Kurz zusammengefasst – drei Ebenen in Next.js:
- not-found.tsx → 404, Seite existiert nicht
- error.tsx → Laufzeitfehler
- global-error.tsx → Root-Layout-Fallback
Technisch ist das überschaubar. Die Herausforderung ist Design. Eine gute Fehlerseite kann die Absprungrate von 78 % auf 42 % senken – gemessen, nicht geraten.
Suchfeld, ein paar Empfehlungslinks, ein menschlicher Satz – oft reicht das.
Schauen Sie in Ihr Next.js-Projekt: Noch Standard-Fehlerseiten? Eine halbe Stunde Aufwand – Nutzer werden es merken.
Fragen? Schreiben Sie gerne. Wenn der Artikel geholfen hat, teilen Sie ihn mit anderen Entwicklern.
Benutzerdefinierte Next.js-404-Seite erstellen
Schritt-für-Schritt-Anleitung für eine benutzerdefinierte 404-Fehlerseite im Next.js App Router – mit Suchfeld und Empfehlungslinks
- 1
Step 1: not-found.tsx erstellen
Erstellen Sie im app-Verzeichnis eine not-found.tsx-Datei als globale 404-Seite - 2
Step 2: Basis-UI-Komponente hinzufügen
Importieren Sie die Next.js Link-Komponente und erstellen Sie eine Grundoberfläche mit Fehlermeldung und Zurück-zur-Startseite-Button - 3
Step 3: 'use client'-Direktive hinzufügen
Fügen Sie am Dateianfang 'use client' hinzu, wenn Sie State-Management oder Interaktionen (z. B. Suchfeld) benötigen - 4
Step 4: Suchfunktion implementieren
Verwenden Sie useState für die Sucheingabe und useRouter für die Suchnavigation - 5
Step 5: Beliebte Links hinzufügen
Erstellen Sie ein Array mit empfohlenen Seitenlinks und rendern Sie die Navigation mit der Link-Komponente - 6
Step 6: Styling optimieren
Gestalten Sie die Seite mit Tailwind CSS oder einer anderen Styling-Lösung und halten Sie die Markenidentität konsistent - 7
Step 7: 404 in Seitenkomponenten auslösen
Rufen Sie in page.tsx dynamischer Routen notFound() auf, wenn die angeforderten Daten nicht existieren - 8
Step 8: Testen und validieren
Rufen Sie einen nicht existierenden Pfad auf und prüfen Sie in den Browser-DevTools, ob der HTTP-Status 404 ist
FAQ
Was ist der Unterschied zwischen not-found.tsx, error.tsx und global-error.tsx in Next.js?
Warum ist der HTTP-Status nach notFound() 200 statt 404?
Warum kann global-error.tsx kein Tailwind CSS oder CSS-Dateien importieren?
Wie kann ich nachverfolgen, welche nicht existierenden Seiten Nutzer aufrufen?
Welche Elemente sollte eine benutzerdefinierte 404-Seite enthalten, um die Absprungrate zu senken?
15 Min. Lesezeit · Veröffentlicht am: 5. Jan. 2026 · Aktualisiert am: 14. Juli 2026
Next.js Komplettleitfaden
Wenn du über die Suche hier gelandet bist, kommst du am schnellsten weiter, indem du zum vorherigen oder nächsten Beitrag dieser Serie springst.
Vorheriger
Next.js Loading-Zustandsverwaltung: loading.tsx und Suspense – Praxisleitfaden
Lernen Sie loading.tsx und Suspense in Next.js praktisch einzusetzen – ohne manuelles useState, mit minimalem Code und professionellem Ladeerlebnis. Inklusive Skeleton Screens, dynamischer Routen und häufiger Problemlösungen.
Teil 32 von 51
Nächster
Next.js Error Boundary: Vollständiger Leitfaden – 5 Tipps für elegante Laufzeitfehlerbehandlung
Next.js Error Boundary im Detail: error.tsx, globales Error Handling, Besonderheiten bei Server Components und Wiederherstellungsmechanismen – so vermeiden Sie weiße Bildschirme und verbessern die UX.
Teil 34 von 51
Ähnliche Beiträge
Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick

Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick
Next.js 15 in der Praxis: Wie ich an einem Wochenende ein produktionsreifes Blog-System aufgebaut habe


Kommentare
Melde dich mit GitHub an, um einen Kommentar zu hinterlassen