Zurück zum Blog
Jul 16, 2026
Engineering
Greta Redaktionsteam

Datei-Uploads und Speicherung in einer KI-gebauten App handhaben

Dateien über den eigenen Server zu leiten, ist das Upload-Muster, das in einer Demo funktioniert und in der Produktion versagt. Warum signierte URLs und direkte Uploads zum Speicher das Muster sind, das wirklich skaliert.

Datei-Uploads und Speicherung in einer KI-gebauten App handhaben

Datei-Uploads und Speicherung in einer KI-gebauten App handhaben

Kurzantwort

Lass eine Datei niemals über deinen eigenen Server laufen. Das richtige Muster ist eine signierte URL: dein Backend bittet den Storage-Provider um eine kurzlebige Erlaubnis, der Browser lädt direkt zu diesem Provider hoch, und dein Server sieht am Ende nur den finalen Pfad und die Metadaten der Datei. Machst du es anders, hast du dir einen langsamen, speicherhungrigen Upload-Endpunkt gebaut, der beim ersten Versuch, ein 200-MB-Video zu senden, in die Knie geht.

Warum "die Datei einfach auf dem eigenen Server entgegennehmen" scheitert

Der erste Instinkt der meisten Leute ist der einfachste: ein Formularfeld hinzufügen, an eine API-Route posten, die Bytes auf die Festplatte oder in eine Datenbankspalte schreiben. In der Demo funktioniert das. Profilbild hochladen, es rendern sehen, ausliefern.

Dann lädt jemand ein 40-MB-PDF hoch. Deine Serverless-Function hat ein Payload-Limit --- oft 4,5 MB, manchmal weniger --- und der Request schlägt einfach fehl, ohne brauchbare Fehlermeldung, nur ein Timeout oder ein 413. Oder es klappt, aber jetzt hält dein Serverprozess die komplette Datei im Speicher, während er sie woanders hin weiterlädt --- für einen Nutzer kein Problem, für fünfzig gleichzeitige ein echtes.

Ich habe Teams stundenlang debuggen sehen in der Annahme, es sei ein Bug im eigenen Code. Ist es nicht. Es ist ein Architekturproblem: Der Server sollte nie in der Mitte dieser Übertragung stehen.

Das Anzeichen, dass du es falsch machst

Wenn die Aufgabe deiner API-Route die Wörter "empfangen" und "weiterleiten" im selben Satz enthält --- die Datei empfangen, an den Storage weiterleiten ---, hast du einen Hop eingebaut, der nicht existieren müsste, und einen Flaschenhals, der mit deinem Traffic skaliert statt mit dem deines Storage-Providers.

Das Muster, das tatsächlich skaliert

Ein funktionierender Upload-Flow hat drei Schritte, und keiner davon leitet die Datei über deinen Anwendungsserver.

Schritt eins --- signierte URL anfordern. Der Browser sagt deinem Backend: "Ich möchte eine Datei namens invoice.pdf hochladen, ein PDF, ungefähr 2 MB." Dein Backend prüft, ob dieser Nutzer hier überhaupt hochladen darf, und fragt dann den Storage-Provider (S3, Supabase Storage, Cloudflare R2 --- was auch immer du nutzt) nach einer vorsignierten URL, die exakt auf diese Datei begrenzt ist und ein paar Minuten gültig bleibt.

Schritt zwei --- direkt hochladen. Der Browser nutzt diese URL, um die Datei per PUT direkt zum Storage zu senden. Dein Server wird während dieses Teils überhaupt nicht berührt. Ein 500-MB-Video verbraucht dieselben Serverressourcen wie ein 5-KB-Thumbnail: null.

Schritt drei --- bestätigen und speichern. Sobald der Upload fertig ist, teilt der Browser (oder ein Storage-Webhook, falls der Provider das unterstützt) deinem Backend mit: "fertig, hier ist der finale Pfad." Dein Backend schreibt diesen Pfad in die Datenbank, führt jede Validierung durch, die vorher nicht möglich war, und die Datei ist jetzt Teil der Daten deiner App.

Das ist dieselbe Form, egal ob du Nutzer-Avatare speicherst oder Massen-CSV-Importe handhabst --- nur der Validierungsschritt ändert sich.

Was "Validierung, die vorher nicht möglich war" tatsächlich bedeutet

Eine signierte URL legt Dateiname und ungefähre Größe fest, kann aber keinen Inhalt prüfen, den sie noch nicht gesehen hat. Genau hier zählt eine zweite Ebene: Nach dem Upload den tatsächlichen Dateityp anhand der Bytes prüfen (nicht der Endung, die ein Nutzer eingegeben hat), alles ablehnen, was nicht mit der Angabe übereinstimmt, und den Gesamtspeicher pro Konto begrenzen, damit nicht ein Nutzer heimlich deinen Bucket füllt. Nichts davon ist exotisch --- es sind ein paar Zeilen im Bestätigungsschritt ---, aber es zu überspringen ist der Grund, warum Apps am Ende eine .exe ausliefern, die in .jpg umbenannt wurde.

Ein Vergleich: proxied Uploads vs. Direct-to-Storage

AspektServer-proxied UploadDirect-to-Storage (signierte URL)
Maximal praktikable DateigrößeBegrenzt durch Function-Payload-Caps (oft ~4,5 MB)Nur begrenzt durch den Storage-Provider (mehrere GB)
Serverlast beim UploadSkaliert mit gleichzeitigen UploadsPraktisch null --- Server ist nicht im Übertragungspfad
Time to First Byte bei großen DateienLangsam --- Server puffert, dann leitet weiterSchnell --- direkte Verbindung zum CDN-Edge des Storage
Aufwand zum EinrichtenAm Anfang niedriger (ein Endpunkt)Etwas mehr (Request für signierte URL + Bestätigungsschritt)
Wo es brichtSobald Dateigrößen oder Nebenläufigkeit wachsenSelten --- so machen es produktive Apps im großen Maßstab tatsächlich

Wo das in einer von Greta gebauten App lebt

Weil Greta eine Next.js-App mit Prisma und Cloud-Storage von Anfang an einrichtet, ist das Signed-URL-Muster keine zusätzliche Infrastruktur, die man später anflanscht --- es ist die Standardform eines Upload-Features. Eine Route wie app/api/uploads/sign/route.ts übernimmt die Berechtigungsprüfung und fragt beim Storage die signierte URL an; eine documents- oder attachments-Tabelle im Prisma-Schema speichert den finalen Pfad, den Content-Type und den Eigentümer, sobald der Upload bestätigt ist. Die Bild-Transformationen und die CDN-Auslieferung, die Thumbnails schnell laden lassen, übernimmt die Storage-Ebene --- nichts, was du von Grund auf schreibst.

Das zählt bei allem mit echtem Dateivolumen --- ein CRM, bei dem jeder Kontakt Dokumente angehängt hat, ein Marktplatz, bei dem jedes Listing Fotos hat, ein internes Tool, bei dem jemand wöchentlich eine Tabelle hochlädt. Das Muster ändert sich nicht; nur die Dateitypen und Validierungsregeln.

FAQ

Kann ich Dateien einfach als Blob-Spalte in meiner Datenbank speichern? Kannst du, und für eine Handvoll kleiner Dateien wird nichts sichtbar kaputtgehen. Aber Datenbankspeicher ist pro Gigabyte teurer als Object Storage, Backups werden langsamer, je größer die Blob-Tabelle wird, und du verlierst die CDN-Auslieferung, die Bilder und Videos schnell laden lässt. Nutze die Datenbank für Metadaten und Pfad der Datei, nicht für die Bytes selbst.

Was ist mit Dateigrößenlimits für kostenlose vs. zahlende Nutzer? Das ist eine Geschäftsregel, keine technische --- prüfe sie im Schritt "signierte URL anfordern", bevor der Upload überhaupt startet, damit ein Nutzer über seinem Kontingent eine klare Meldung bekommt statt eines verschwendeten Uploads.

Brauche ich Virenscans? Für eine Consumer-App, bei der Nutzer ihre eigenen Dateien für den eigenen Gebrauch hochladen, in der Regel nicht am ersten Tag. Für alles, wo die Datei eines Nutzers später von einem anderen Nutzer geöffnet wird --- ein gemeinsamer Workspace, ein Support-Ticket-Anhang ---, ja, und dieser Scan sollte Teil des Bestätigungsschritts sein, bevor die Datei für alle anderen als verfügbar markiert wird.

Wie handhabe ich Bildgrößenänderungen für verschiedene Bildschirmgrößen? Die meisten Storage-Provider oder CDNs davor unterstützen On-the-fly-Transformationen über URL-Parameter --- dasselbe Bild in verschiedenen Breiten anfragen und das Resizing und Caching dem Edge überlassen, statt selbst fünf Versionen jedes Uploads zu generieren und zu speichern.

Abschluss

Datei-Uploads wirken wie ein gelöstes Problem --- bis jemand eine echte Datei sendet: ein Video, ein Stapel gescannter Rechnungen, eine Design-Datei, die eigentlich 80 MB groß ist. Baue das Signed-URL-Muster von Anfang an ein, und dieser Tag ist ein Nicht-Ereignis. Überspringe es, und es ist ein Produktionsvorfall mit deinem Namen im Post-Mortem.

Beginne noch heute, deine App mit Greta zu bauen.

Ende des Artikels
Zurück nach oben

Baue etwas Echtes

Wenn du es beschreiben kannst, kannst du es bauen.