Next.js-Entwickler in Essen

Next.js für Essener Unternehmen von einer Person: error.js, global-error.js und not-found, instrumentation.ts mit onRequestError, OpenTelemetry, was ins Log gehört und was nicht, und was die LDI NRW zu Datenpannen sagt.

Next.js-Entwickler

Mein Platz in der WordPress-Community

  • Mein wordpress.org-Konto besteht seit 2006.
  • Ich habe zum WordPress-Core in den Versionen 7.0 und 7.1 beigetragen.
  • Ich betreue 30 Plugins auf wordpress.org.
  • Ich organisiere WordCamps und trete dort als Speaker auf.
  • Ich war bei jedem WordCamp Europe von 2017 bis 2026 dabei.

Mein WordPress.org-Profil

Mariusz Szatkowski hier: Ich entwickle allein von Gdynia aus und arbeite seit 2006 mit WordPress. Für Essen schreibe ich hier über den Teil einer Next.js-Anwendung, der erst auffällt, wenn etwas schiefgeht: Fehlerseiten, Logs und Alarme. Alle Angaben zu Next.js stammen aus der Dokumentation auf nextjs.org, Stand der Seiten Oktober und November 2026. Die Codebeispiele zu Fehlerseite und onRequestError habe ich mit Version 16.4.6 gebaut und laufen lassen, die laut npm-Registry am 9. November 2026 erschien.

#Welche Dateien fangen welchen Fehler?

Die Dokumentation trennt zwei Fehlerarten. Erwartete Fehler gehören zum normalen Betrieb, etwa eine fehlgeschlagene Validierung oder ein Request, der nicht klappt. Sie sollen als Rückgabewert modelliert werden, nicht als geworfene Exception. Alles andere sind unerwartete Ausnahmen. Die wirft man, und eine Error Boundary fängt sie.

DateiFängtWichtig
error.jsFehler im Segment und darunterumschließt loading.js, not-found.js, page.js und verschachtelte Layouts, aber nicht layout.js oder template.js im selben Segment
global-error.jsFehler im Root-Layoutersetzt das Layout, braucht eigene html- und body-Tags, kein metadata-Export
not-found.jsnotFound() im Segmentdie Datei im Root fängt auch alle URLs, die zu keiner Route passen
global-not-found.jsURLs ohne passende Routelaut Dokumentation experimentell, Schalter experimental.globalNotFound
catchErroreinzelne KomponentenBoundary unabhängig von Routensegmenten

Zwei Grenzen stehen ausdrücklich in der Referenz zu error.js. Fehler in Event-Handlern und in asynchronem Code fängt keine Boundary, weil sie nach dem Rendern laufen. Dort fängt man selbst und legt den Fehler in den State. Und global-error.js rendert ohne Ihre globalen Styles, deshalb muss die Seite ihr eigenes Aussehen mitbringen. Seit Version 16.3.0 ist die Prop retry stabil, sie holt und rendert die Kinder der Boundary neu. reset löscht nur den Fehlerzustand und rendert ohne neuen Abruf.

Eine Fehlerseite in dieser Form genügt als Grundgerüst:

// app/dashboard/error.tsx
'use client'

export default function Error({
  error,
  retry,
}: {
  error: unknown
  retry: () => void
}) {
  const digest =
    error instanceof Error && 'digest' in error ? String(error.digest) : undefined

  return (
    <div role="alert">
      <h2>Das hat leider nicht geklappt.</h2>
      {digest && <p>Fehlernummer: {digest}</p>}
      <button onClick={() => retry()}>Erneut versuchen</button>
    </div>
  )
}

Der Typ unknown stammt aus der Dokumentation: Ein Fehler aus einer Client Component kommt unverändert an und muss nicht einmal ein Error sein, deshalb die Prüfung vor dem Zugriff auf digest.

#Warum der Browser nur eine Nummer sieht

Im Produktivbetrieb bekommt der Browser bei Fehlern aus Server Components laut Dokumentation eine allgemeine Meldung mit einer Kennung. Das soll verhindern, dass sensible Details nach außen gelangen. Die Kennung heißt error.digest, ist ein automatisch erzeugter Hash des Fehlers und passt zum Eintrag im Server-Log. In meinem Testaufbau stand dieselbe Nummer im Next.js-Log, in der Antwort an den Browser und in der Zeile, die onRequestError schrieb.

Daraus folgt ein praktischer Ablauf, den ich für Anwendungen mit Kundenkontakt vorschlage: Die Fehlerseite zeigt die Nummer an, Ihr Support bekommt sie genannt und findet den Fall im Log, ohne die Person nach Details zu fragen. Das Muster kommt ohne personenbezogene Angaben aus.

#Was bei 404 und 200 wirklich passiert

Hier liegt die häufigste Fehlannahme im Monitoring. Laut Dokumentation zu loading.js beginnen gestreamte Seitenantworten mit Status 200, und die ersten Header legen den Status für den Rest der Antwort fest. Zu not-found.js steht: Vor dem Streamstart liefert Next.js einen 404. Nach dem Streamstart bleibt der Status 200, und die Not-Found-Oberfläche erscheint trotzdem. In beiden Fällen setzt Next.js ein <meta name="robots" content="noindex" />. Manche Crawler führen solche Antworten als Soft-404.

Das gilt auch für Fehler. In meinem Testaufbau lieferte eine Seite, die ohne Suspense-Grenze beim Rendern abbrach, Status 500. Eine zweite Seite mit Suspense-Grenze, deren Fehler erst nach dem ersten Byte auftrat, lieferte Status 200, und onRequestError meldete den Fehler trotzdem. Das Ergebnis für den Betrieb: Ein Alarm, der nur auf 5xx wartet, übersieht einen Teil der Fehler. Wer den Statuscode für Compliance oder Analytics braucht, prüft laut Dokumentation vor dem Streamstart, etwa im Proxy, und gibt dort 404, 403 oder 401 zurück.

Doppelbock-Fördergerüst über Schacht XII der Zeche Zollverein in Essen

Foto: Anil Öztas, CC BY-SA 4.0, Wikimedia Commons.

#instrumentation.ts: wo Server-Fehler zusammenlaufen

Die Referenz zu instrumentation.js beschreibt zwei Exporte. register läuft einmal pro neuer Serverinstanz und muss fertig sein, bevor der Server Anfragen annimmt. onRequestError meldet Serverfehler an einen Anbieter Ihrer Wahl, seit Version 15.0.0. Die Datei liegt im Projektstamm oder, falls vorhanden, in src.

Die Funktion bekommt drei Argumente. context sagt, wo es geknallt hat: routePath, routeType (render, route, action oder proxy), renderSource und routerKind. Zwei Hinweise der Dokumentation sind wichtig. Asynchrone Arbeit in der Funktion muss mit await abgewartet werden. Und die error-Instanz muss nicht die ursprünglich geworfene sein, weil React sie beim Rendern von Server Components verändern kann. Dann hilft digest.

Das Beispiel der Dokumentation schickt request und context komplett an einen Endpunkt. Das ist der Punkt, an dem ich bei Projekten bremse. In meinem Test enthielt request.path den Query-String (/boom?email=a%40b.de) und request.headers unter anderem cookie und x-forwarded-for. Die Fehlermeldung, die im Test eine E-Mail-Adresse nannte, stand auch in der Konsolenausgabe von Next.js selbst. Wer das Beispiel unverändert übernimmt, schickt Cookies an einen Dritten. Stattdessen baue ich die Zeile aus einer Positivliste:

// instrumentation.ts
import type { Instrumentation } from 'next'

export const onRequestError: Instrumentation.onRequestError = async (
  err,
  request,
  context
) => {
  const digest =
    typeof err === 'object' && err !== null && 'digest' in err
      ? String(err.digest)
      : undefined

  console.error(
    JSON.stringify({
      level: 'error',
      digest,
      errorName: err instanceof Error ? err.name : typeof err,
      route: context.routePath,
      routeType: context.routeType,
      method: request.method,
      path: request.path.split('?')[0],
    })
  )
}

Die Zeile enthält Nummer, Fehlertyp, Route, Kontext, Methode und den Pfad ohne Query. Die Meldung des Fehlers fehlt mit Absicht, ebenso die Header. Ob Sie einzelne Routen doch mit Meldung brauchen, klären wir schriftlich, zusammen mit einer Frist für die Löschung. Dazu gleich mehr.

#OpenTelemetry: was Next.js mitbringt

Die Anleitung zu OpenTelemetry empfiehlt OpenTelemetry für die Instrumentierung und sagt, Next.js sei bereits selbst instrumentiert. Es gibt zwei Wege. @vercel/otel ist die kurze Variante: registerOTel({ serviceName: 'next-app' }) in register. Es läuft laut Dokumentation auf Vercel und bei Selbstbetrieb mit einem OpenTelemetry Collector. Die manuelle Variante nutzt NodeSDK aus einer eigenen Datei instrumentation.node.ts, die nur bei process.env.NEXT_RUNTIME === 'nodejs' geladen wird, weil NodeSDK nicht in der Edge-Runtime läuft. Wer die Edge-Runtime braucht, muss @vercel/otel nehmen.

Für den Datenschutz sind drei Details der Seite relevant:

  • Der Wurzel-Span jeder Anfrage heißt [http.method] [next.route] und trägt unter anderem http.route und http.target. Ob dort Query-Strings landen, prüfe ich im Collector, bevor Daten den Server verlassen.
  • Der Span für fetch-Aufrufe führt http.url. Steckt in einer Backend-URL eine Kundennummer, steht sie im Trace. Der Span lässt sich mit NEXT_OTEL_FETCH_DISABLED=1 abschalten, falls eine eigene fetch-Instrumentierung übernimmt.
  • Mit NEXT_OTEL_VERBOSE=1 erzeugt Next.js mehr Spans als üblich. Das ist für die Fehlersuche gedacht, nicht als Dauerzustand.

Eigene Spans legt man mit der OpenTelemetry-API an. Die Seite warnt, dass Spans innerhalb von register oder vor dem Start des Tracer-Providers nicht zur Anfrage gehören. Ein Span gehört dorthin, wo die Anfrage verarbeitet wird, also in die Seite, den Route Handler oder die Server Action.

#Was ins Log gehört und was nicht

Das Standard-Datenschutzmodell, das die LDI NRW auf ihrer Seite vorstellt, hat einen eigenen Baustein 43 zum Protokollieren. Die aktuelle Fassung 2.0 gilt laut Seite des Landesbeauftragten Mecklenburg-Vorpommern seit dem 1. Juni 2026. Der Baustein sagt im Kern: Für Protokolle gelten die Grundsätze aus Art. 5 DS-GVO. Im Regelfall soll man auf Personen- und Inhaltsdaten im Protokollbestand verzichten, und wo der Personenbezug nötig ist, muss das begründet sein. Für Bestände mit personenbezogenen Daten müssen Löschfristen festgelegt sein. Zeitstempel sollen verlässlich und standardisiert sein, damit sich Abläufe über mehrere Systeme nachvollziehen lassen.

Daraus entsteht für eine Next.js-Anwendung diese Aufteilung:

FeldIns Log?Begründung
digest, Fehlertyp, routePath, routeTypejatechnisch, ohne Personenbezug
Methode, Pfad ohne Queryjareicht, um die Route zu finden
Zeitstempel im einheitlichen Format, Release-Kennung aus Ihrer CIjaverknüpft Fehler mit Version und Systemen
Query-Stringneinkann E-Mail-Adressen, Token, Suchbegriffe enthalten
cookie, authorizationneinSitzungsdaten gehören in kein Fremdsystem
x-forwarded-for, IP-AdresseneinPersonenbezug, nur mit Begründung und Löschfrist
error.message als Freitextneinkann Daten aus der Anfrage enthalten
Formularinhalte, Request-BodyneinInhaltsdaten
Nutzerkennungnur mit Begründungpseudonym, aber personenbezogen, Löschfrist festlegen

Die Löschfrist trägt Ihr Verantwortlicher ein, sie hängt vom Zweck ab. Ich liefere die Feldliste und die Konfiguration des Log-Ziels, sodass die Frist technisch durchgesetzt wird.

#Wenn doch Personendaten im Log landen

Ein Log mit E-Mail-Adressen, das ein Drittanbieter speichert, ist ein Fall, den man bewerten muss. Die LDI NRW erklärt zur Meldepflicht:

  • Verletzungen des Schutzes personenbezogener Daten sind der Aufsichtsbehörde unverzüglich, möglichst binnen 72 Stunden, zu melden, wenn mehr als nur ein geringes Risiko für die Betroffenen besteht (Art. 33 DS-GVO).
  • Bei hohem Risiko sind zusätzlich die Betroffenen zu informieren (Art. 34 DS-GVO).
  • Jede Verletzung ist intern zu dokumentieren, auch wenn keine Meldung nötig ist (Art. 33 Abs. 5 DS-GVO).
  • Passiert die Verletzung bei einem Auftragsverarbeiter, muss er den Verantwortlichen unverzüglich informieren. Der Verantwortliche prüft dann seine Pflichten.
  • Die LDI NRW stellt dafür ein Meldeformular auf ihrer Website bereit.

Zwei Folgen für den Aufbau. Erstens: Der Anbieter Ihres Log- oder Fehlerdienstes ist in der Regel Auftragsverarbeiter, und der Vertrag sollte regeln, wie schnell er Sie informiert. Zweitens: Wer die Zeile im Log von vornherein klein hält, hat im Ernstfall weniger zu bewerten. Ob in einem konkreten Fall eine Meldung nötig ist, entscheiden Sie mit Ihrer Rechtsberatung oder Ihrer Datenschutzbeauftragten. Das ist keine Rechtsberatung.

#Alarme, die nicht ermüden

Alarme sind in der Next.js-Dokumentation kein Thema. Was jetzt folgt, ist meine Praxis und kein Zitat. Ich halte vier Auslöser für sinnvoll, alle ohne feste Schwellenwerte, die ich Ihnen aus der Luft nennen müsste:

  1. Neue Fehlergruppe. Ein digest oder eine Kombination aus Route und Fehlertyp, die es in der vorigen Version nicht gab. Das ist das stärkste Signal nach einem Release.
  2. Auffälliger Anstieg je routeType. Fehler in Server Actions (action) treffen Formulare und Bestellungen, Fehler beim render betreffen Seiten.
  3. Ein Prüfaufruf mit Inhaltsvergleich. Weil gestreamte Fehler Status 200 liefern, ruft ein geplanter Test die wichtigen Seiten ab und sucht einen festen Text, nicht nur den Statuscode.
  4. Anstieg bei 404 auf Routen, die es geben sollte. Ein Datenfehler kann notFound() auslösen. Dann sieht die Seite nach einem Fehler wie eine fehlende Seite aus.

Alarme gehen an eine benannte Person, und zu jedem gehört ein kurzer Satz im Betriebshandbuch: was zu prüfen ist, wer entscheidet, wann abgeschaltet wird. Ein Alarm, den niemand zuordnen kann, wird irgendwann ignoriert.

#Checkliste vor dem Livegang

FrageWarum sie zählt
Gibt es error.js auf den Routen mit Daten und ein global-error.js im Root?Sonst zeigt ein Fehler im Layout eine Standardseite
Zeigt die Fehlerseite die Nummer an?Der Support findet den Fall ohne Rückfragen
Ist der 404-Pfad bewusst gebaut (notFound(), Proxy oder beides)?Statuscode und noindex hängen vom Zeitpunkt ab
Schreibt onRequestError aus einer Positivliste?Cookies und Query-Strings bleiben draußen
Sind http.url und http.target im Trace geprüft?Personenbezug kann in URLs stecken
Ist eine Löschfrist für das Log festgelegt und technisch umgesetzt?Gefordert, wenn Personendaten im Protokoll stehen
Gibt es einen AV-Vertrag mit dem Log-Anbieter?Er ist Auftragsverarbeiter
Prüft ein Test Inhalte statt nur Statuscodes?Gestreamte Fehler liefern 200
Hat jeder Alarm einen Eigentümer?Sonst Alarmmüdigkeit

#So läuft ein Projekt mit mir

Ich arbeite allein, aus der Ferne und schriftlich, ohne Termine und Telefonate. Am Anfang steht Ihre Mail mit der Adresse der Anwendung, dem Ziel und der Frist. Ich lese den Code, stelle meine Fragen schriftlich und schicke eine Beschreibung des Umfangs mit den Annahmen. Ich baue auf einer Staging-Umgebung, Sie prüfen dort und geben schriftlich frei, erst dann geht die Anwendung live. Für dieses Thema liefere ich die Fehlerdateien, die instrumentation.ts, die Konfiguration von OpenTelemetry, die Feldliste des Logs für Ihr Verzeichnis der Verarbeitungstätigkeiten und eine Betriebsbeschreibung für Ihr Team. Wollen Sie die laufende Pflege abgeben, beschreibt die Seite Wartung und Support in Essen den Ablauf. Das Layout der Fehlerseiten liefern Sie als Vorlage, ich baue es nach. Ist die Anwendung im Kern eine Inhaltsseite, prüfe ich vorab, ob Astro weniger Betrieb bedeutet, und für den Weg zwischen beiden gibt es die Seite zur Migration zwischen Next.js und Astro in Essen. Für ein PHP-Backend hinter dem Frontend gibt es die Seite zur PHP-Entwicklung in Essen.

#Häufige Fragen

Wann greift error.js und wann global-error.js? error.js fängt Fehler in seinem Segment und darunter, aber nicht im layout.js oder template.js desselben Segments. Fehler im Root-Layout fängt nur global-error.js. Diese Datei ersetzt das Layout, braucht eigene html- und body-Tags und kennt keine metadata-Exporte.

Warum zeigt die Fehlerseite nur eine Nummer und nicht die Meldung? Next.js reicht Fehler aus Server Components im Produktivbetrieb nur mit allgemeiner Meldung an den Browser, damit keine Details nach außen gelangen. Die Nummer steht in error.digest und passt zum Eintrag im Server-Log. Wer sie anzeigt, kann einen Fall im Log wiederfinden.

Warum meldet das Monitoring Status 200, obwohl die Seite kaputt ist? Gestreamte Antworten beginnen laut Dokumentation mit Status 200, und der Status ändert sich danach nicht mehr. Ein Fehler, der erst nach dem Streamstart auftritt, erscheint im Statuscode also nicht. Er erscheint in onRequestError. Darum alarmiere ich auf diese Ereignisse und prüfe zusätzlich Inhalte, nicht nur Statuscodes.

Was gehört nicht ins Log? Query-Strings, Cookies, Authorization-Header, IP-Adressen, Formularinhalte und freie Fehlertexte, die Daten aus der Anfrage enthalten können. Das Standard-Datenschutzmodell sieht im Regelfall keine Personen- und Inhaltsdaten im Protokoll vor und verlangt Löschfristen, wenn doch welche darin stehen.

Brauche ich OpenTelemetry? Für reine Fehlermeldungen reicht onRequestError. OpenTelemetry liefert zusätzlich Traces über Anfrage, Rendering und fetch-Aufrufe. Die Next.js-Dokumentation empfiehlt es für die Instrumentierung. Ob der Aufwand für Ihre Anwendung lohnt, kläre ich nach Ihrer Beschreibung schriftlich mit Ihnen.

Gestalten Sie auch die Fehlerseiten? Nein. Layout, Texte und Gestaltung kommen von Ihnen oder Ihrer Agentur. Ich baue die Seiten nach Ihrer Vorlage und verdrahte sie mit den richtigen Dateien und Logs.

#Ihre Anwendung in einer E-Mail

Fehler tauchen oft zuerst bei Ihren Kunden auf. Wenn Sie wissen wollen, was Ihre Next.js-Anwendung im Ernstfall meldet und was dabei im Log landet, schreiben Sie mir über das Kontaktformular. Nennen Sie die Adresse der Anwendung, Ihr Ziel, den Wunschtermin und den aktuellen Stack samt Hosting. Ich antworte an Werktagen schriftlich mit meinen Fragen und einer ersten Schätzung, und die ist kostenlos.

Brauchen Sie die Leistung: Next.js-Entwickler in Essen?

Beschreiben Sie Ihr Projekt per E-Mail. Antwort innerhalb von 48 Stunden.

Projekt beschreiben

Weitere WordPress-Dienste und Wissensbasis entdecken