<v/>
{ }
Vue 3 · Dateiupload · Axios · FileReader · UX
Dateiuploads in Vue: Progress, Preview und Fehlerpfade
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.

14 Min. Lesezeit Upload-Progress · Datei-Preview · Drag-and-Drop · Validierung · Retry Vue 3 · Axios · FileReader API · TypeScript

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.

11. FAQ: Dateiuploads in Vue mit Progress, Preview und Fehlerpfaden

1Upload-Fortschritt mit Axios anzeigen?
onUploadProgress-Option in Axios: event.loaded / event.total * 100. Als reaktives ref speichern und im Template als Balken visualisieren.
2createObjectURL vs. FileReader?
createObjectURL: speichereffizient, synchron. FileReader: liest Dateiinhalt als base64 in den RAM. Für Bildvorschauen immer createObjectURL.
3isDragging flackert bei Kindelementen?
Counter statt Boolean: dragenter erhöht, dragleave verringert. isDragging = counter > 0. Kein Flackern bei verschachtelten Elementen.
4Axios-Upload abbrechen?
CancelTokenSource erstellen, als cancelToken übergeben. source.cancel() bricht ab. Im catch mit axios.isCancel() prüfen.
5Client-seitige Validierung?
MIME-Type über file.type, nicht nur Dateiendung. file.size für Größe. Alle Fehler gesammelt melden, nicht nacheinander.
6Wann Chunked Upload?
Ab 100 MB oder instabiler Verbindung. Chunked ermöglicht Resume. Für die meisten Apps reicht Axios + Retry.
7Speicherlecks bei Vorschauen verhindern?
Object-URLs im Array verfolgen und in onUnmounted alle freigeben. Beim Entfernen einer Datei sofort revokeObjectURL aufrufen.
8Retry-Logik implementieren?
Exponentieller Backoff: 1s, 2s, 4s. Max. 3 Versuche. Nur 5xx und Netzwerkfehler wiederholen. 4xx nicht.
9Composable oder Pinia-Action für Upload?
Composable. Upload-Zustand ist lokal zur Komponente. Globaler Store nur wenn andere Teile auf Upload-Status reagieren müssen.
10Upload-Timeouts bei großen Dateien?
Axios-Timeout auf 60–120 Sekunden. Bei ECONNABORTED verständliche Meldung. Für große Dateien: Server antwortet mit Job-ID, Client pollt Status.