
TypeError: Failed to fetch ist der Browser, der Ihnen sagt, dass ein fetch()-Aufruf nie
abgeschlossen wurde. Nicht, dass er fehlgeschlagen ist - dass er nie weit genug kam, um überhaupt
ein Ergebnis zu haben.
Dieser Unterschied ist wichtiger als alles andere auf dieser Seite, denn er schließt das meiste aus,
was man zuerst vermutet. Ein 404 erzeugt das nicht. Ein 500 ebenso wenig. Das sind abgeschlossene
Anfragen: der Server hat geantwortet, fetch() wird erfüllt, und response.ok ist false. Failed
to fetch heißt, dass es gar kein Response-Objekt zu prüfen gibt, und genau deshalb sagt Ihnen die
Meldung so wenig.
Chrome, Firefox und Safari formulieren es jeweils anders - Failed to fetch, NetworkError when
attempting to fetch resource, Load failed - und meinen dasselbe.
Die Ursachen, in der Reihenfolge, in der sie tatsächlich vorkommen
CORS. Mit Abstand die häufigste. Die Anfrage ging hinaus, der Server hat geantwortet, und der Browser hat sich geweigert, die Antwort an Ihren Code zu übergeben, weil die Header das nicht erlaubten. Die Konsole gibt eine eigene Meldung zur Origin-Policy aus, und der fetch wird mit dem generischen Fehler abgelehnt. Wenn Sie nur die Ablehnung lesen, erfahren Sie nichts; die Zeile darüber ist die eigentliche Antwort.
Die Verbindung kam nie zustande. Offline, DNS-Fehler, Host nicht erreichbar, Server hört nicht zu. Das ist dasselbe Gebiet wie die ERR_CONNECTION-Familie, und das Netzwerkpanel benennt es.
Etwas hat sie blockiert. Ein Werbeblocker, eine Privatsphäre-Erweiterung, ein Unternehmens-Proxy. Anfragen an alles, was nach Analytics oder Tracking aussieht, werden abgewiesen, bevor sie den Browser verlassen, und das Netzwerkpanel markiert sie als blockiert. Auf Ihrer eigenen Maschine ist das unsichtbar, wenn Sie der Einzige ohne diese Erweiterung sind.
Gemischte Inhalte. Eine https-Seite fragt eine http-URL an. Der Browser blockiert das
rundheraus.
Die Anfrage wurde abgebrochen. Die Seite ist weiternavigiert, eine Komponente wurde ausgehängt,
ein AbortController hat ausgelöst, der Tab wurde mitten in der Anfrage geschlossen. In einer
Single-Page-Anwendung ganz gewöhnlich, und im Error-Tracking erscheint es als Flut von
Fehlschlägen, die niemandem je passiert sind.
Eine falsche URL. Ein Tippfehler, ein relativer Pfad, der irgendwo landet, wo er nicht landen
sollte, eine Umgebungsvariable, die beim Build leer war, sodass die Anfrage an undefined/api/thing
ging.
Failed to fetch dynamically imported module
Eine besondere Variante, die einen eigenen Absatz verdient, denn die Ursache liegt nicht in Ihrem Code und die Lösung auch nicht.
Eine Anwendung mit Code-Splitting lädt Chunks mit gehashten Dateinamen. Sie deployen, die Hashes ändern sich, die alten Dateien hören auf zu existieren. Wer die Seite schon offen hat - ein über Nacht offen gelassener Tab, ein Telefon, das nie neu geladen hat - fragt einen Dateinamen an, der jetzt ein 404 ist, und bekommt diesen Fehler in dem Moment, in dem er zu einer Route navigiert, deren Chunk noch nicht geladen ist.
Zwei Lösungen, und sie greifen ineinander. Behalten Sie die Assets des vorherigen Builds eine Weile, statt sie zu ersetzen, damit offene Tabs weiter funktionieren. Und fangen Sie den Fehlschlag im Router ab: wenn ein dynamischer Import abgelehnt wird, bieten Sie ein Neuladen an, denn ein Neuladen behebt es wirklich.
Wie Sie herausfinden, welche davon Sie haben
Die Konsole sagt es Ihnen nicht. Das Netzwerkpanel schon, und es dauert etwa zehn Sekunden.
Öffnen Sie es, provozieren Sie den Fehlschlag noch einmal und suchen Sie die rote Anfrage. Dann lesen Sie drei Spalten:
-
Status. Leer oder
(failed)heißt, die Anfrage wurde nie abgeschlossen.(blocked:...)benennt das, was sie blockiert hat. - Der Host. Ihre eigene Domain oder die von jemand anderem. Ein Fehlschlag gegen einen Dritten ist ein anderes Problem als einer gegen Ihre eigene API.
- Die Konsolenmeldung direkt über der Ablehnung. Wenn dort von einer Origin oder einem Preflight die Rede ist, haben Sie ein CORS-Problem, und alles andere auf dieser Seite ist Ablenkung.
Wenn die Anfrage im Netzwerkpanel gar nicht auftaucht, hat sie den Browser nie verlassen: gemischte Inhalte, eine Erweiterung oder eine URL, die nie gültig war.
Session Replay
Kostenlose Chrome-Erweiterung. Ein Klick auf der Seite, die sich falsch verhält, erfasst den Screenshot, die Konsole und das Netzwerkprotokoll und gibt Ihnen einen Link, den Sie ins Ticket einfügen können.
Warum das der schlimmste Fehler ist, den man aus zweiter Hand bekommt
Alles oben setzt voraus, dass Sie ihn reproduzieren können. Meistens können Sie das nicht, denn die Ursachen, die diesen Fehler erzeugen, sind gerade die, die auf der Maschine eines anderen leben: dessen Erweiterung, dessen Netzwerk, dessen Unternehmens-Proxy, dessen veralteter Tab.
Also kommt er als “auf der Seite steht nur, dass etwas schiefgelaufen ist” an, und die
Konsolenmeldung, die man Ihnen vielleicht zitiert, Failed to fetch, ist die eine Meldung auf der
Seite, die keinerlei Information trägt. Der Beleg, der die Sache klären würde, ist eine Zeile in
einem Netzwerkpanel, in das niemand schaut.
Das ist das Argument dafür, das Netzwerkprotokoll in dem Moment zu erfassen, in dem es passiert, statt hinterher Fragen zu stellen. Eine HAR-Datei ist der manuelle Weg dorthin. So oder so entscheidet bei dieser Art von Fehler die Zeile, nicht der Satz.
Die Kurzfassung
- Es heißt, dass die Anfrage nie abgeschlossen wurde. Keine Antwort, kein Statuscode, nichts zu prüfen.
- Ein 404 oder ein 500 verursacht ihn nicht - das sind erfolgreiche Hin- und Rückwege mit unerfreulichen Antworten.
- Prüfen Sie zuerst die Konsolenzeile über der Ablehnung. Wenn dort CORS steht, ist das Ihre Antwort.
- Prüfen Sie dann, ob die Anfrage überhaupt im Netzwerkpanel auftaucht. Wenn nicht, hat etwas im Browser sie gestoppt, bevor sie hinausging.
- Nach einem Deploy heißt “failed to fetch dynamically imported module”, dass ein alter Tab eine Datei anfragt, die Ihr letzter Build gelöscht hat.