von der Dateiauswahl bis zum robusten Retry-Mechanismus
Ein Dateiupload ohne Fortschrittsanzeige, ohne Vorschau und ohne verständliche Fehlermeldungen frustriert Nutzer. Vue 3 bietet mit der Composition API, der FileReader-API und Axios-Upload-Progress alle Bausteine für einen professionellen Dateiupload — wenn man weiß, wie sie zusammenspielen.
Inhaltsverzeichnis
- 1. Dateiupload in Vue: Grundlagen und Fallstricke
- 2. File-Input und Drag-and-Drop sauber implementieren
- 3. Client-seitige Validierung vor dem Upload
- 4. Datei-Preview mit der FileReader-API
- 5. Upload-Progress mit Axios onUploadProgress
- 6. Das useFileUpload-Composable als wiederverwendbare Einheit
- 7. Fehlerpfade: Netzwerkfehler, Server-Fehler und Timeouts
- 8. Retry-Logik und Abbruch laufender Uploads
- 9. Upload-Strategien im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Dateiupload in Vue: Grundlagen und Fallstricke
Ein Dateiupload in Vue beginnt scheinbar einfach: ein <input type="file">, ein Event-Handler und ein Axios-POST. Die Schwierigkeit liegt nicht im Happy Path, sondern in den Randszenarien: Was passiert, wenn die Verbindung während des Uploads abbricht? Was sieht der Nutzer, wenn eine Datei das Größenlimit überschreitet? Wie verhält sich die Benutzeroberfläche bei mehreren gleichzeitigen Uploads? Diese Fragen zu beantworten, bevor sie in der Produktion auftreten, ist der Kern eines durchdachten Dateiupload in Vue-Konzepts.
Der häufigste Fehler bei der Implementierung von Dateiuploads in Vue ist fehlende Zustandstrennung: Upload-Fortschritt, Vorschau, Validierungsfehler und Server-Antwort werden in einer einzigen, unübersichtlichen Komponente verwaltet. Das führt zu schwer testbarem Code und verhindert die Wiederverwendung der Upload-Logik an anderen Stellen der Anwendung. Die Lösung ist ein dediziertes useFileUpload-Composable, das den gesamten Upload-Lebenszyklus kapselt und der Komponente nur eine saubere, reaktive API nach außen gibt.
2. File-Input und Drag-and-Drop sauber implementieren
Das native <input type="file"> ist der Ausgangspunkt jeden Dateiuploads in Vue. Es lässt sich mit CSS vollständig unsichtbar machen und durch einen beliebig gestalteten Button oder eine Drag-and-Drop-Zone ersetzen, während das Input-Element die eigentliche Dateiauswahl übernimmt. Über eine Template-Ref auf das Input-Element lässt sich programmatisch inputEl.value.click() aufrufen — so reagiert ein beliebiges HTML-Element auf Klicks mit dem Dateiauswahl-Dialog, ohne dass das Input-Element sichtbar sein muss.
Für Drag-and-Drop registriert man auf der Drop-Zone die Events dragenter, dragleave, dragover und drop. Das dragover-Event muss mit event.preventDefault() behandelt werden, da der Browser sonst das Datei-Öffnen verhindert. Die Unterscheidung zwischen dragenter und dragleave bei verschachtelten Kindelementen ist ein klassischer Fallstrick: Wenn der Mauszeiger vom übergeordneten Drop-Bereich in ein Kind-Element wechselt, feuert dragleave auf dem Elternelement, obwohl die Maus noch innerhalb der Zone ist. Die robuste Lösung ist ein Counter-Ansatz statt eines Boolean-Flags für den Drag-Zustand — im Composable erhöht dragenter den Counter, dragleave verringert ihn, und isDragging ist genau dann true, wenn der Counter größer null ist.
<!-- FileDropZone.vue — accessible drag-and-drop upload area -->
<template>
<div
class="border-2 border-dashed rounded-xl p-8 text-center transition-colors"
:class="isDragging ? 'border-green-500 bg-green-50' : 'border-slate-300 bg-slate-50'"
@dragenter.prevent="dragCount++"
@dragleave.prevent="dragCount--"
@dragover.prevent
@drop.prevent="onDrop"
@click="fileInput?.click()"
role="button"
:aria-label="label"
>
<input
ref="fileInput"
type="file"
class="sr-only"
:accept="accept"
:multiple="multiple"
@change="onFileChange"
/>
<slot :isDragging="isDragging">
<p class="text-slate-500 text-sm">
Dateien hierher ziehen oder <span class="text-green-700 font-semibold">klicken zum Auswählen</span>
</p>
</slot>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
const props = defineProps<{
accept?: string
multiple?: boolean
label?: string
}>()
const emit = defineEmits<{
filesSelected: [files: File[]]
}>()
// Counter-based drag detection — avoids false dragleave on child elements
const dragCount = ref(0)
const isDragging = computed(() => dragCount.value > 0)
const fileInput = ref<HTMLInputElement | null>(null)
function onDrop(event: DragEvent) {
dragCount.value = 0
const files = Array.from(event.dataTransfer?.files ?? [])
if (files.length) emit('filesSelected', files)
}
function onFileChange(event: Event) {
const input = event.target as HTMLInputElement
const files = Array.from(input.files ?? [])
if (files.length) emit('filesSelected', files)
// Reset input so the same file can be re-selected
input.value = ''
}
</script>
3. Client-seitige Validierung vor dem Upload
Die Validierung ist der erste Filter in jedem Dateiupload in Vue: Sie verhindert unnötige Netzwerkanfragen und gibt dem Nutzer sofortiges Feedback. Die wichtigsten Prüfungen sind Dateityp, Dateigröße und Dateianzahl bei Mehrfach-Uploads. Der Dateityp sollte nicht nur anhand der Dateiendung, sondern auch anhand des MIME-Types geprüft werden, den das Betriebssystem für die Datei meldet. Eine Datei namens virus.jpg.exe hat den MIME-Type application/x-msdownload — eine reine Endungs-Prüfung würde sie durchlassen.
Die Dateigröße liest man über file.size in Bytes aus. Für eine aussagekräftige Fehlermeldung rechnet man in lesbare Einheiten um: Bytes, Kilobytes, Megabytes. Die Validierungsfunktion sollte ein typisiertes Ergebnisobjekt zurückgeben, das alle fehlgeschlagenen Prüfungen mit Grund enthält — nicht nur true oder false. So kann die Benutzeroberfläche spezifische Meldungen wie "Datei überschreitet das Limit von 10 MB" oder "Nur JPEG und PNG sind erlaubt" anzeigen, statt einer generischen Fehlermeldung, die den Nutzer im Dunkeln lässt.
4. Datei-Preview mit der FileReader-API
Die FileReader-API ermöglicht es, eine lokale Datei als Data-URL in den Browser zu laden, bevor sie auf den Server hochgeladen wird. Für Bilder ergibt das eine sofortige Vorschau, die dem Nutzer bestätigt, dass die richtige Datei ausgewählt wurde. Die Integration in einen Dateiupload in Vue ist ein klassischer Anwendungsfall für eine Promise-Wrapper-Funktion: Das callback-basierte FileReader-API in eine Promise-Funktion zu verpacken macht sie direkt mit await verwendbar und vereinfacht das Fehler-Handling erheblich.
Ein wichtiger Aspekt bei der Vorschau-Implementierung ist das Speichermanagement: Data-URLs sind Strings, die den gesamten Dateiinhalt base64-kodiert enthalten. Bei großen Bildern kann das mehrere Megabyte im Arbeitsspeicher belegen. Für große Dateien ist URL.createObjectURL(file) die bessere Alternative: Es erstellt eine temporäre URL, die auf den Speicherbereich der Datei zeigt, ohne den Inhalt zu duplizieren. Diese Object-URLs müssen manuell mit URL.revokeObjectURL(url) freigegeben werden — am besten in einem Vue onUnmounted-Hook oder wenn die Vorschau nicht mehr benötigt wird.
// composables/useFilePreview.ts
// Generates file previews using Object URLs for memory efficiency
import { ref, onUnmounted } from 'vue'
export interface FilePreview {
file: File
previewUrl: string | null
isImage: boolean
formattedSize: string
}
export function useFilePreview() {
const previews = ref<FilePreview[]>([])
// Track created object URLs for cleanup
const objectUrls: string[] = []
function createPreview(file: File): FilePreview {
const isImage = file.type.startsWith('image/')
let previewUrl: string | null = null
if (isImage) {
// createObjectURL is memory-efficient — no base64 duplication
previewUrl = URL.createObjectURL(file)
objectUrls.push(previewUrl)
}
return {
file,
previewUrl,
isImage,
formattedSize: formatBytes(file.size),
}
}
function addFiles(files: File[]) {
previews.value.push(...files.map(createPreview))
}
function removeFile(index: number) {
const preview = previews.value[index]
if (preview.previewUrl) {
URL.revokeObjectURL(preview.previewUrl)
}
previews.value.splice(index, 1)
}
// Release all object URLs when component unmounts
onUnmounted(() => {
objectUrls.forEach(url => URL.revokeObjectURL(url))
})
return { previews, addFiles, removeFile }
}
function formatBytes(bytes: number): string {
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
}
5. Upload-Progress mit Axios onUploadProgress
Axios unterstützt das XMLHttpRequest-onprogress-Event direkt über die Konfiguration onUploadProgress. Die Callback-Funktion empfängt ein ProgressEvent mit den Feldern loaded (übertragene Bytes) und total (Gesamtgröße). Der Fortschritts-Prozentsatz ergibt sich aus Math.round((loaded / total) * 100). Im Composable wird dieser Wert als reaktives ref gehalten und in der Vorlage als Breite einer Fortschrittsleiste oder als Textanzeige verwendet. Der Dateiupload in Vue fühlt sich für den Nutzer damit lebendig an: er sieht, dass etwas passiert, und kann abschätzen, wann der Upload abgeschlossen sein wird.
Beim Hochladen von mehreren Dateien gleichzeitig empfiehlt sich eine Fortschrittsanzeige pro Datei sowie ein Gesamtfortschritt. Den Gesamtfortschritt berechnet man aus der Summe der übertragenen Bytes aller aktiven Uploads geteilt durch die Summe ihrer Gesamtgrößen — nicht als Durchschnitt der prozentualen Werte, da das bei Dateien unterschiedlicher Größe falsche Ergebnisse liefert. Eine große Datei, die zu 10 % hochgeladen ist, und eine kleine Datei, die zu 90 % hochgeladen ist, ergeben einen echten Gesamtfortschritt von weit unter 50 %, obwohl der Durchschnitt der Prozentwerte 50 % betrüge.
6. Das useFileUpload-Composable als wiederverwendbare Einheit
Das useFileUpload-Composable kapselt den gesamten Dateiupload in Vue-Lebenszyklus: Dateiauswahl, Validierung, Vorschau-Generierung, den eigentlichen Upload mit Fortschritt, Fehlerbehandlung und Cleanup nach dem Upload. Die Komponente, die es nutzt, kümmert sich nur noch um das Template: Sie zeigt an, was das Composable an reaktiven Werten liefert, und ruft Methoden auf, wenn der Nutzer interagiert. Das Ergebnis ist eine Komponente, die vollständig testbar ist, weil die gesamte Upload-Logik in einer Funktion steckt, die unabhängig von Vue geeinigt werden kann.
Das Interface des Composables gibt nach außen: files (Array der gewählten Dateien mit Vorschau und Fortschritt), isUploading (Boolean), totalProgress (0–100), errors (Array von Fehler-Objekten), addFiles(), removeFile(), upload() und cancelAll(). Dieses Interface ist vollständig und abgeschlossen: Nichts, was außerhalb des Composables ist, muss Implementierungsdetails des Uploads kennen. Andere Stellen der Anwendung, die Dateiuploads in Vue benötigen, rufen dieselbe Funktion auf und bekommen ein identisches Interface.
7. Fehlerpfade: Netzwerkfehler, Server-Fehler und Timeouts
Fehlerpfade beim Dateiupload in Vue teilen sich in drei Kategorien: Netzwerkfehler (keine Verbindung, Verbindungsabbruch), Server-Fehler (4xx, 5xx HTTP-Statuscodes) und Client-seitige Timeouts. Axios wirft für alle drei Fälle Fehler, die man mit axios.isAxiosError(error) erkennt und dann anhand von error.response (Server-Fehler), error.request (kein Server erreicht) und error.code === 'ECONNABORTED' (Timeout) unterscheidet. Eine gute Fehlerbehandlung übersetzt diese technischen Details in verständliche Meldungen auf Deutsch, die dem Nutzer sagen, was er tun kann.
Ein besonders häufiger Fehlerpfad in produktiven Anwendungen ist der Timeout bei großen Dateien: Der Server empfängt die Datei, verarbeitet sie (z.B. Bildkonvertierung), und die Verbindung wird durch einen Proxy-Timeout getrennt, bevor die Antwort zurückkommt. Die Lösung ist nicht, den Timeout zu verlängern, sondern den Upload vom Verarbeitungs-Status zu trennen: Der Server antwortet sofort mit einer Job-ID, der Client pollt den Status asynchron. Für einfachere Anwendungsfälle reicht ein kalibierter Timeout von 60–120 Sekunden plus eine klare Meldung beim Ablauf, statt den Nutzer in eine endlose Wartezeit ohne Rückmeldung zu schicken.
// api/uploadApi.ts — typed upload function with progress and cancellation
import axios, { type AxiosProgressEvent, type CancelTokenSource } from 'axios'
export interface UploadOptions {
onProgress?: (percent: number) => void
cancelToken?: CancelTokenSource
}
export interface UploadResult {
fileId: string
url: string
filename: string
}
export async function uploadFile(
file: File,
endpoint: string,
options: UploadOptions = {}
): Promise<UploadResult> {
const formData = new FormData()
formData.append('file', file)
try {
const response = await axios.post<UploadResult>(endpoint, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
timeout: 120_000, // 2-minute timeout for large files
cancelToken: options.cancelToken?.token,
onUploadProgress(event: AxiosProgressEvent) {
if (event.total) {
const percent = Math.round((event.loaded / event.total) * 100)
options.onProgress?.(percent)
}
},
})
return response.data
} catch (error) {
if (axios.isCancel(error)) {
throw new Error('Upload abgebrochen')
}
if (axios.isAxiosError(error)) {
if (!error.response) throw new Error('Keine Verbindung zum Server. Bitte Internetverbindung prüfen.')
if (error.code === 'ECONNABORTED') throw new Error('Upload-Timeout. Bitte mit einer kleineren Datei versuchen.')
if (error.response.status === 413) throw new Error('Datei zu groß. Maximum: 50 MB.')
if (error.response.status === 415) throw new Error('Dateiformat nicht unterstützt.')
throw new Error(`Server-Fehler (${error.response.status}). Bitte später erneut versuchen.`)
}
throw error
}
}
8. Retry-Logik und Abbruch laufender Uploads
Netzwerkfehler bei Dateiuploads in Vue sind temporär: ein kurzer Verbindungsabbruch, ein überlasteter Server, ein Proxy-Neustart. Eine eingebaute Retry-Logik verbessert die Nutzererfahrung erheblich, weil der Nutzer nicht manuell neu laden muss. Das grundlegende Muster: maximal drei Versuche mit exponentiellem Backoff — nach dem ersten Fehler 1 Sekunde warten, nach dem zweiten 2 Sekunden, nach dem dritten 4 Sekunden. Server-Fehler mit 4xx-Status-Codes werden nicht wiederholt, weil sie auf einen Anwendungsfehler hinweisen, der durch erneutes Senden nicht behoben wird. Nur 5xx-Server-Fehler und Netzwerkfehler rechtfertigen einen Retry-Versuch.
Das Abbrechen eines laufenden Uploads funktioniert in Axios über Cancel-Tokens. Ein CancelTokenSource-Objekt wird beim Start des Uploads erstellt und die cancel()-Methode aufgerufen, wenn der Nutzer abbricht. Im Composable speichert man für jeden laufenden Upload ein Cancel-Token, sodass einzelne Uploads aus einer Mehrfach-Upload-Warteschlange abgebrochen werden können, ohne die anderen zu beeinflussen. Nach dem Abbruch wird der Zustand der betroffenen Datei auf cancelled gesetzt und die Vorschau-URL freigegeben. Das ist ein vollständiger Fehlerpfad des Dateiuploads in Vue, der ohne diese Behandlung oft einfach ignoriert wird.
9. Upload-Strategien im Vergleich
Je nach Anforderung gibt es verschiedene technische Ansätze für Dateiuploads in Vue. Die Wahl hängt von Dateigröße, Netzwerkzuverlässigkeit und Server-Architektur ab.
| Strategie | Geeignet für | Vorteile | Nachteile |
|---|---|---|---|
| Einfacher POST | Dateien < 10 MB | Einfach, keine Server-Logik | Kein Fortschritt, kein Resume |
| Axios mit Progress | Dateien < 100 MB | Fortschritt, Cancel, Retry | Kein Resume bei Abbruch |
| Chunked Upload | Dateien > 100 MB | Resume-fähig, stabil | Server-seitige Chunk-Logik nötig |
| Presigned URL (S3) | Große Dateien, Cloud-Storage | Kein Server-Bandwidth-Overhead | CORS-Konfiguration, URL-Ablauf |
| tus-Protokoll | Zuverlässigkeit kritisch | Resume, offenes Protokoll | Abhängigkeit von tus-Server |
Für die meisten Web-Anwendungen ist Axios mit Progress der pragmatische Standard. Chunked Uploads lohnen sich ab Dateigrößen von über 100 MB oder wenn eine schlechte Netzwerkverbindung der Nutzer zu erwarten ist. Presigned URLs sind die bevorzugte Lösung, wenn Dateien direkt in Cloud-Storage (AWS S3, Google Cloud Storage) landen sollen, ohne den Anwendungsserver als Durchlaufstation zu belasten.
Mironsoft
Vue 3 Frontend-Entwicklung, Upload-Infrastruktur und UX-Engineering
Dateiupload-Feature ohne robuste Fehlerbehandlung?
Wir implementieren vollständige Dateiupload-Lösungen in Vue 3 – mit Progress, Preview, Retry und sauberer Fehlerbehandlung für alle Fehlerpfade, die im Produktionsbetrieb auftreten.
Upload-Composable
Vollständige Upload-Logik als wiederverwendbares Composable mit TypeScript
Fehlerpfade
Alle Netzwerk-, Server- und Timeout-Szenarien vollständig abgedeckt
UX-Review
Upload-UX auf Basis echter Nutzerfeedback-Muster optimieren
10. Zusammenfassung
Ein professioneller Dateiupload in Vue besteht aus mehreren Schichten: Drag-and-Drop mit Counter-basierter Drag-Erkennung, client-seitiger Validierung vor dem Upload, Vorschau-Generierung mit Object-URLs statt Data-URLs, Upload-Fortschritt über Axios onUploadProgress, und vollständiger Fehlerbehandlung für Netzwerkfehler, Server-Fehler und Timeouts. Der useFileUpload-Composable kapselt diese Logik und gibt der Komponente eine saubere, reaktive API ohne interne Implementierungsdetails.
Der größte Hebel für die Nutzererfahrung ist eine klare Fehlerbehandlung: Nutzer, die wissen warum ein Upload fehlgeschlagen ist und was sie tun können, brechen nicht einfach ab. Retry-Logik mit exponentiellem Backoff für temporäre Netzwerkfehler, der Cancel-Token für bewusste Abbrüche und eine klare Unterscheidung zwischen Client-Validierungsfehlern und Server-Fehlern machen den Dateiupload in Vue zu einer zuverlässigen Funktion statt einer Fehlerquelle.
Dateiupload in Vue — Das Wichtigste auf einen Blick
Drag-and-Drop
Counter-Ansatz für isDragging statt Boolean – verhindert Flackern bei verschachtelten Kindelementen. Input-Reset nach Auswahl ermöglicht Re-Selektion derselben Datei.
Vorschau & Speicher
URL.createObjectURL() statt Data-URL für Bilder. Object-URLs in onUnmounted mit URL.revokeObjectURL() freigeben – kein Speicherleck.
Upload-Progress
Axios onUploadProgress liefert loaded und total. Gesamtfortschritt über Byte-Summen berechnen, nicht als Durchschnitt der Prozentwerte.
Fehlerpfade
Netzwerkfehler, 4xx, 5xx und Timeout separat behandeln. Retry nur für 5xx und Netzwerkfehler. Cancel-Token für Benutzer-Abbrüche.