fix: "Unexpected token '<'" bei Uploads durch die echte Ursache ersetzen

Alle drei Admin-Upload-Felder riefen response.json() ungeprueft auf. Kam
statt JSON eine HTML-Seite zurueck, warf der Parser

  Unexpected token '<', "<!DOCTYPE "... is not valid JSON

und genau dieser Text landete im Formular. Damit war die eigentliche
Ursache vollstaendig verdeckt: weder der HTTP-Status noch die Schicht,
die abgebrochen hat, waren daraus ablesbar.

Der Punkt dabei: die Upload-Route-Handler antworten ausnahmslos mit JSON,
auch in jedem Fehlerfall — lokal verifiziert bis hinauf zur
Limit-Ueberschreitung (105 MB -> 413 mit JSON-Body). Trifft im Browser
trotzdem HTML ein, stammt die Antwort gar nicht aus der Anwendung,
sondern aus einer Schicht davor: Reverse-Proxy, CDN oder Gateway, das den
Request wegen zu grossem Body abgewiesen oder wegen Zeitueberschreitung
beim Transcoding abgebrochen hat.

readUploadResponse() in lib/upload-client.ts prueft deshalb zuerst den
Content-Type und reicht andernfalls den HTTP-Status nach oben, statt zu
parsen. Die Felder zeigen daraus eine Meldung, die den Status nennt und
die wahrscheinliche Ursache benennt (413 = vom Proxy abgewiesen,
502/503/504/522/524 = Zeitueberschreitung beim Umrechnen, sonst
unerwartet). Ein Verbindungsabbruch, bei dem fetch() selbst wirft, wird
getrennt gemeldet.

Das behebt nicht die Ursache jenseits der Anwendung — es macht sie zum
ersten Mal ablesbar, statt sie hinter einem Parser-Fehler zu verbergen.
This commit is contained in:
Nick Szaunig committed 2026-09-14 00:36:10 +02:00
1 parent be5fb32a4a
commit b11edd7751
8 files changed
+219 -15

No files matched your search

+14
View File
@@ -1139,6 +1139,13 @@ export const en: Dictionary = {
remove: "Remove image",
removeImageAt: (position) => `Remove image ${position}`,
uploadError: "The upload failed.",
uploadErrorStatus: (status) =>
status === 413
? `The file was rejected because it is too large (HTTP 413). It never reached the shop — that limit comes from the reverse proxy or CDN in front of it.`
: status === 502 || status === 503 || status === 504 || status === 522 || status === 524
? `The server aborted processing (HTTP ${status}). This is almost always a timeout: processing takes longer than the proxy in front of it waits for a response.`
: `The server responded unexpectedly (HTTP ${status}) — no usable reply. Please report this number to your technical contact.`,
uploadErrorNetwork: "The connection to the server was interrupted during the upload.",
},
adminVideoField: {
none: "No video",
@@ -1146,6 +1153,13 @@ export const en: Dictionary = {
uploading: "Uploading…",
remove: "Remove video",
uploadError: "The upload failed.",
uploadErrorStatus: (status) =>
status === 413
? `The video was rejected because it is too large (HTTP 413). It never reached the shop — that limit comes from the reverse proxy or CDN in front of it, not from the shop itself.`
: status === 502 || status === 503 || status === 504 || status === 522 || status === 524
? `The server aborted processing (HTTP ${status}). For videos this is almost always a timeout: re-encoding for the web takes longer than the proxy in front of it waits for a response. A shorter or smaller video usually gets through.`
: `The server responded unexpectedly (HTTP ${status}) — no usable reply. Please report this number to your technical contact.`,
uploadErrorNetwork: "The connection to the server was interrupted during the upload.",
},
adminShell: {
navDashboard: "Dashboard",