<v/>
{ }
Vue 3 · Admin-UI · Tabellen · Pinia · TypeScript
Admin-Oberflächen mit Vue 3
Tabellen, Filter und Bulk-Actions professionell umsetzen

Admin-Dashboards sind die technisch anspruchsvollsten UIs einer Applikation – sortierbare Datentabellen mit tausenden Einträgen, verschachtelte Filter, URL-synchronisierter Zustand und Bulk-Actions auf ausgewählten Datensätzen stellen hohe Anforderungen an Architektur und Performance. Vue 3's Composition API und Pinia bieten genau die richtigen Werkzeuge, um Admin-Oberflächen wartbar und schnell zu bauen.

22 Min. Lesezeit Tabellen · Filter · Bulk-Actions · Paginierung · URL-Sync Vue 3.4+ · Pinia · Vue Router · TypeScript

1. Was Admin-Oberflächen mit Vue 3 besonders macht

Eine Admin-Oberfläche mit Vue 3 stellt andere Anforderungen als eine Marketing-Website oder ein E-Commerce-Frontend. Benutzer sind interne Mitarbeiter oder Administratoren, die dieselbe Oberfläche täglich stundenlang verwenden – Performance, Tastatur-Bedienbarkeit und konsistentes Verhalten sind daher nicht Komfort, sondern Produktivitätsfaktor. Eine Tabelle, die beim Sortieren jedesmal einen vollständigen API-Request auslöst und dabei alle Filterparameter verliert, kostet pro Nutzung Sekunden – multipliziert über hundert Nutzende und tausend Interaktionen täglich entsteht daraus messbarer Verlust.

Vue 3's Composition API löst die spezifischen Probleme von Admin-Oberflächen besser als jedes andere Frontend-Framework der gleichen Gewichtsklasse. Die klare Trennung zwischen UI-Zustand (Sortierrichtung, Selektion, Ladezustand) und Datenzustand (die eigentlichen Datensätze) durch Pinia-Stores und Composables macht komplexe Admin-Interaktionen überschaubar. URL-synchronisierter Filter-Zustand gibt Nutzenden die Möglichkeit, gefilterte Ansichten zu bookmarken und zu teilen. Bulk-Actions auf ausgewählten Datensätzen mit optimistischen Updates und Rollback bei Fehler machen die Oberfläche professionell. Diese Kapitel zeigen, wie man Admin-Oberflächen mit Vue 3 Schritt für Schritt aufbaut.

2. Sortierbare Datentabelle mit Vue 3 und Composition API

Der Kern jeder Admin-Oberfläche in Vue 3 ist die Datentabelle. Das Design einer wartbaren Tabellen-Komponente folgt einem klaren Prinzip: Die Tabelle selbst rendert ausschließlich – sie sortiert, filtert und lädt keine Daten selbst. Ein separates useDataTable-Composable enthält die gesamte Logik für Sortierzustand, Selektion, Paginierung und API-Calls. Das Template der Tabellenkomponente empfängt Daten und Callback-Props und gibt Selektion und Sortierklicks durch Events nach oben. Diese Trennung macht die Tabellen-Komponente in mehreren Kontexten mit unterschiedlichen Datenquellen wiederverwendbar.

Sortierung in einer Vue 3 Admin-Tabelle verwaltet man als ein Objekt { column: string, direction: 'asc' | 'desc' } in einem reaktiven Ref. Ein Klick auf eine Spalten-Überschrift wechselt die Richtung, wenn die Spalte bereits aktiv ist, oder setzt die Spalte als aktiv mit aufsteigender Richtung. Das Sortier-Objekt wird bei Server-Side-Tabellen direkt als Query-Parameter an die API weitergegeben; bei Client-Side-Tabellen mit kleinen Datensätzen wird ein computed mit Array.sort() genutzt. Wichtig: Array.sort() mutiert das Array in-place – das Original-Array sollte eine shallowRef-Kopie sein, nicht das Array aus dem API-Response, um Reaktivitäts-Seiteneffekte zu vermeiden.


// composables/useDataTable.ts — Reusable admin table logic
import { ref, computed, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import type { Ref } from 'vue'

interface SortState {
  column: string
  direction: 'asc' | 'desc'
}

interface TableOptions {
  defaultSort?: SortState
  pageSize?: number
  syncUrl?: boolean
}

export function useDataTable<T extends { id: number | string }>(
  fetchFn: (params: Record<string, unknown>) => Promise<{ data: T[]; total: number }>,
  options: TableOptions = {}
) {
  const { defaultSort = { column: 'id', direction: 'desc' }, pageSize = 25, syncUrl = true } = options

  const route = useRoute()
  const router = useRouter()

  // UI state — separate from data state
  const sort = ref<SortState>(defaultSort)
  const currentPage = ref(1)
  const selectedIds = ref<Set<number | string>>(new Set())

  // Data state
  const rows = ref<T[]>([]) as Ref<T[]>
  const total = ref(0)
  const isLoading = ref(false)
  const error = ref<Error | null>(null)

  // Derived
  const totalPages = computed(() => Math.ceil(total.value / pageSize))
  const allSelected = computed(() =>
    rows.value.length > 0 && rows.value.every(r => selectedIds.value.has(r.id))
  )

  async function load() {
    isLoading.value = true
    error.value = null
    try {
      const params = {
        sort: sort.value.column,
        direction: sort.value.direction,
        page: currentPage.value,
        perPage: pageSize,
      }
      const result = await fetchFn(params)
      rows.value = result.data
      total.value = result.total
      selectedIds.value.clear() // Clear selection on new load
    } catch (err) {
      error.value = err as Error
    } finally {
      isLoading.value = false
    }
  }

  function setSort(column: string) {
    if (sort.value.column === column) {
      sort.value = { column, direction: sort.value.direction === 'asc' ? 'desc' : 'asc' }
    } else {
      sort.value = { column, direction: 'asc' }
    }
    currentPage.value = 1 // Reset to page 1 on sort change
  }

  function toggleSelect(id: number | string) {
    if (selectedIds.value.has(id)) selectedIds.value.delete(id)
    else selectedIds.value.add(id)
  }

  function toggleSelectAll() {
    if (allSelected.value) selectedIds.value.clear()
    else rows.value.forEach(r => selectedIds.value.add(r.id))
  }

  // Reload on sort or page change
  watch([sort, currentPage], load, { deep: true })

  return {
    rows, total, totalPages, isLoading, error,
    sort, currentPage, selectedIds, allSelected,
    load, setSort, toggleSelect, toggleSelectAll,
  }
}

3. Filter-Composable mit URL-Synchronisierung

Filter in einer Admin-Oberfläche mit Vue 3 müssen in der URL gespeichert werden – das ist keine Komfortfunktion, sondern eine Produktivitätsanforderung. Wenn ein Administrator eine Liste auf "Status: Ausstehend, Erstellt: Letzte 7 Tage, Kategorie: Elektronik" filtert und dann in einen Datensatz klickt, muss der Zurück-Button die exakt gleiche gefilterte Liste wiederherstellen. Ohne URL-Synchronisierung sind alle Filtereinstellungen verloren, wenn der Nutzer die Seite verlässt. Vue Router bietet mit useRoute und useRouter einen reaktiven Zugriff auf die aktuelle URL, der sich ideal für diesen Zweck nutzen lässt.

Das Filter-Composable-Pattern für Vue 3 Admin-Oberflächen: Ein useTableFilters-Composable initialisiert den Filter-State aus dem aktuellen URL-Query-String, hält ihn als reaktives Objekt und synchronisiert Änderungen zurück in die URL. Die API-Parameter werden als computed aus dem Filter-State abgeleitet – wann immer sich der Filter ändert, ändert sich das computed, was automatisch einen Watch-Trigger im Datentabellen-Composable auslöst. Debouncing für Freitext-Felder stellt sicher, dass nicht bei jedem Tastendruck ein API-Request ausgelöst wird. Ein "Filter zurücksetzen"-Button setzt alle Filter auf ihre Standardwerte und navigiert zur URL ohne Query-Parameter.

4. Server-Side-Sorting und -Filterung

Für Admin-Oberflächen mit Vue 3, die viele tausend Datensätze verwalten, ist Client-Side-Sorting und -Filterung keine Option – alle Daten auf einmal zu laden und im Browser zu sortieren ist bei großen Datenmengen weder performant noch praktikabel. Server-Side-Sorting sendet Sortierparameter an die API und erhält bereits sortierte Daten zurück. Das bedeutet, dass jede Sortier- oder Filteränderung einen neuen API-Request auslöst. Das Vue 3 Pattern für diesen Fall: Request-Cancellation mit AbortController. Wenn der Nutzer schnell nacheinander mehrere Filter ändert, soll der vorherige Request abgebrochen werden, bevor der neue losgeht – das verhindert Race-Conditions, bei denen ein langsamer Request nach einem schnelleren ankommt und ältere Daten zeigt.

Ein weiteres wichtiges Admin-Oberflächen-Pattern: optimistische Suchvorschläge durch eine separates Autocomplete-API, die mit einem sehr kleinen limit-Parameter nur wenige Matches zurückgibt. Die Haupt-Datentabelle erhält die vollständige Filterliste erst nach der Bestätigung. Für Datums-Filter verwendet man in Vue 3 Admin-Oberflächen ein DateRange-Objekt mit from und to, die als ISO-8601-Strings serialisiert und in der URL als dateFrom=2026-01-01&dateTo=2026-01-31 gespeichert werden. Das ermöglicht direkte Links auf beliebige Datumsfilter ohne JavaScript-Datumsobjekte in der URL.

5. Zeilen-Selektion und Bulk-Actions

Bulk-Actions sind eines der komplexesten Features in einer Admin-Oberfläche mit Vue 3, weil sie Selektion, Bestätigung, API-Calls, Optimistic Updates, Fehlerbehandlung und Feedback-Meldungen kombinieren müssen. Das grundlegende Selektions-Pattern: Ein Set<id> im Composable speichert ausgewählte IDs. Sets bieten O(1)-Lookup für die isSelected(id)-Prüfung, die in jeder Tabellenzeile aufgerufen wird. Ein computed allSelected prüft, ob alle aktuell sichtbaren Zeilen im Set sind – das steuert den Zustand der "Alle auswählen"-Checkbox im Tabellenkopf. Wichtig: "Alle auswählen" wählt nur die aktuell sichtbaren Zeilen aus, nicht alle Datensätze über alle Seiten – das schützt vor versehentlichen Massen-Operationen.

Das Bulk-Action-Pattern in Vue 3 Admin-Oberflächen folgt einem klaren Ablauf: Nutzer wählt Zeilen, klickt auf Bulk-Action, ein Bestätigungsdialog zeigt Anzahl der betroffenen Datensätze, Bestätigung löst API-Call aus, nach Erfolg werden die betroffenen Zeilen optimistisch aus der Tabelle entfernt und eine Toast-Meldung mit "Rückgängig"-Option erscheint. Das "Rückgängig"-Fenster ist üblicherweise fünf Sekunden lang offen – danach wird die Aktion serverseitig endgültig. Wenn der Nutzer in diesen fünf Sekunden "Rückgängig" klickt, wird ein Restore-API-Call ausgelöst und die Zeilen erscheinen wieder in der Tabelle. Dieses Muster ist aus modernen Admin-Interfaces wie Gmail und Linear bekannt und erheblich benutzerfreundlicher als ein einfaches "Sind Sie sicher?"-Modal.


// composables/useBulkActions.ts — Bulk actions with undo pattern
import { ref, computed } from 'vue'
import type { Ref } from 'vue'

interface BulkActionOptions<T> {
  selectedIds: Ref<Set<number | string>>
  rows: Ref<T[]>
  onSuccess?: (ids: (number | string)[]) => void
}

export function useBulkActions<T extends { id: number | string }>(
  options: BulkActionOptions<T>
) {
  const { selectedIds, rows, onSuccess } = options

  const isExecuting = ref(false)
  const undoQueue = ref<{ ids: (number | string)[]; restore: T[]; timeout: ReturnType<typeof setTimeout> }[]>([])

  async function execute(
    actionFn: (ids: (number | string)[]) => Promise<void>,
    label: string
  ) {
    const ids = Array.from(selectedIds.value)
    if (!ids.length) return

    // Optimistically remove rows from table
    const removedRows = rows.value.filter(r => ids.includes(r.id))
    rows.value = rows.value.filter(r => !ids.includes(r.id))
    selectedIds.value.clear()

    isExecuting.value = true
    try {
      await actionFn(ids)
      onSuccess?.(ids)

      // Set up undo window (5 seconds)
      const timeout = setTimeout(() => {
        undoQueue.value = undoQueue.value.filter(q => q.timeout !== timeout)
      }, 5000)

      undoQueue.value.push({ ids, restore: removedRows, timeout })
    } catch (err) {
      // Rollback on error — restore rows
      rows.value = [...removedRows, ...rows.value]
      rows.value.sort((a, b) => (a.id > b.id ? 1 : -1))
      console.error('Bulk action failed, rolled back:', err)
    } finally {
      isExecuting.value = false
    }
  }

  function undo(index: number) {
    const entry = undoQueue.value[index]
    if (!entry) return
    clearTimeout(entry.timeout)
    rows.value = [...entry.restore, ...rows.value]
    rows.value.sort((a, b) => (a.id > b.id ? 1 : -1))
    undoQueue.value.splice(index, 1)
  }

  return { isExecuting, undoQueue, execute, undo }
}

6. Paginierung mit Cursor- und Offset-Strategie

Paginierung in Admin-Oberflächen mit Vue 3 ist keine triviale UI-Aufgabe – die Wahl zwischen Offset-Paginierung (LIMIT x OFFSET y) und Cursor-Paginierung (WHERE id > last_id LIMIT x) hat direkte Auswirkungen auf Performance und Konsistenz. Offset-Paginierung ist einfach zu implementieren und ermöglicht direktes Springen zu beliebigen Seiten, leidet aber an dem "Phantom-Row"-Problem: Wenn zwischen dem Laden von Seite 1 und Seite 2 ein neuer Datensatz vorne eingefügt wird, erscheint der letzte Datensatz von Seite 1 erneut als erster Datensatz von Seite 2. Cursor-Paginierung vermeidet dieses Problem, erlaubt aber kein direktes Seitenspringen – man kann nur vorwärts und rückwärts navigieren.

Das Vue 3 Pattern für Paginierung in Admin-Oberflächen kombiniert beides: Offset-Paginierung mit direktem Seitenzähler für den normalen Navigationsfall und Cursor-Paginierung für echtzeit-sensitive Feeds wie Aktivitäts-Logs. Die Paginierungskomponente selbst ist eine reine UI-Komponente ohne eigene Logik: sie empfängt currentPage, totalPages und totalItems als Props und emittiert page-change. Pagination-State gehört in die URL: ?page=3 macht einen bestimmten Zustand bookmarkbar. Nach einem Bulk-Delete – wenn die aktuelle Seite durch die gelöschten Einträge leer wird – navigiert das Composable automatisch zur letzten noch verfügbaren Seite.

7. Berechtigungen und bedingte Aktionen

In professionellen Admin-Oberflächen mit Vue 3 ist nicht jede Aktion für jeden Nutzer verfügbar. Ein Lesezugang sieht die Datentabelle, kann aber weder bearbeiten noch löschen. Ein Redakteur kann bearbeiten, aber nicht löschen. Nur ein Administrator sieht Bulk-Löschen und Nutzer-Verwaltung. Das Vue 3 Pattern für Berechtigungen trennt zwei Ebenen: der Router-Guard auf Route-Ebene verhindert das Betreten geschützter Seiten für Nutzer ohne Mindestberechtigung. Ein usePermissions-Composable stellt Methoden wie can('orders:delete') und cannot('users:manage') bereit, die in Templates mit v-if genutzt werden, um Schaltflächen und Menüpunkte zu zeigen oder zu verstecken.

Wichtig bei Admin-Oberflächen: Frontend-Berechtigungsprüfungen sind ausschließlich UX-Hilfsmittel – sie ersetzen niemals serverseitige Autorisierung. Eine "Löschen"-Schaltfläche, die per v-if versteckt ist, hindert einen technisch versierten Nutzer nicht am direkten API-Call. Das Backend muss jede Aktion eigenständig autorisieren. Das Vue 3 Frontend-Pattern optimiert die Benutzererfahrung durch das Verbergen nicht erlaubter Aktionen – aber nie auf Kosten der Server-Sicherheit. Permissions werden beim Login vom Backend als strukturiertes Objekt zurückgegeben und in einem Pinia-Store gespeichert, nicht im LocalStorage allein, der manipulierbar ist.

8. Typische Fehler in Admin-Oberflächen mit Vue

Der häufigste Fehler beim Bau von Admin-Oberflächen mit Vue 3 ist das Mischen von UI-State und Server-State in derselben Komponente. Wenn die Komponente selbst API-Calls macht, Ladezustand verwaltet, Filter hält und gleichzeitig die Tabelle rendert, wird sie schnell unkontrollierbar. Das korrekte Vue 3 Admin-Pattern ist klare Trennung: Composables für Logik, Pinia für geteilten State, Komponenten nur für Rendering. Eine Tabellen-Komponente, die ausschließlich rendert und Events emittiert, ist in Minuten testbar – eine Gott-Komponente mit hundert Zeilen Logik nicht.

Ein zweiter verbreiteter Fehler: Bulk-Actions ohne Bestätigungsschritt und ohne Rückmeldung. Ein Klick auf "Alle auswählen" und dann "Löschen" ohne Modal oder Toast-Feedback ist eine Katastrophe, wenn der Nutzer versehentlich auf den falschen Button geklickt hat. Das Admin-UI-Pattern fordert immer: Bestätigung bei destruktiven Aktionen, optimistisches Feedback direkt nach der Aktion und Undo-Option für mindestens fünf Sekunden. Ein dritter Fehler: Paginierungszustand nicht in der URL speichern. Wenn ein Admin auf Seite 10 einer Tabelle einen Datensatz öffnet, bearbeitet und dann zurücknavigiert, sollte er auf Seite 10 landen – nicht auf Seite 1.

9. Admin-UI-Patterns im direkten Vergleich

Admin-Oberflächen werden oft mit kurzfristigen Lösungen gebaut, die langfristig zu Wartbarkeitsproblemen führen. Ein direkter Vergleich zeigt, welche Vue 3 Admin-Patterns sich langfristig bewähren.

Feature Kurzfristige Lösung Vue 3 Admin-Pattern Vorteil
Filter-State Nur in Komponente URL-synchronisiert Bookmarkbar, Zurück-Button funktioniert
Bulk-Delete "Sind Sie sicher?"-Alert Optimistisch + Toast + Undo Schneller, komfortabler, rückgängig machbar
Paginierung Client-seitig, alle Daten Server-Side, URL-State Skaliert mit Datenmenge, teilbar
Berechtigungen Hardcoded v-if-Checks usePermissions-Composable Zentral, testbar, konsistent
Race-Conditions Kein Abbruch alter Requests AbortController pro Request Keine veralteten Daten im UI

Die Spalte "Kurzfristige Lösung" zeigt nicht schlechte Programmierung, sondern typische Entscheidungen unter Zeitdruck. Die Vue 3 Admin-Patterns in der mittleren Spalte sind zwar initial etwas aufwändiger, zahlen sich aber nach dem ersten Support-Ticket über "Filter weg nach Zurück-Button" oder dem ersten ungewollten Massen-Löschen mehr als aus.

Mironsoft

Vue 3 Admin-Dashboards, Datentabellen und Backend-Oberflächen

Admin-Oberfläche, die Ihr Team wirklich produktiv macht?

Wir bauen professionelle Admin-Dashboards mit Vue 3 – sortierbare Datentabellen, URL-synchronisierte Filter, Bulk-Actions mit Undo und feingranulare Berechtigungssysteme.

Datentabellen

Sortierbar, filterbar, mit Server-Side-Paginierung und Race-Condition-Schutz

Bulk-Actions

Optimistische Updates, Undo-Pattern und vollständige Rollback-Logik

Permissions

Rollenbasierte Berechtigungen mit usePermissions-Composable und Router-Guards

10. Zusammenfassung

Professionelle Admin-Oberflächen mit Vue 3 entstehen nicht durch bloßes Zusammenstecken von UI-Komponenten, sondern durch eine durchdachte Architektur, die Logik, State und Rendering klar trennt. Das useDataTable-Composable ist das Herzstück – es verwaltet Sortierung, Selektion, Paginierung und API-Calls in einer testbaren Einheit. Filter werden in der URL synchronisiert, damit Zustände teilbar und bookmarkbar sind. Bulk-Actions folgen dem Optimistic-Update-Pattern mit Undo-Option statt destructiven Bestätigungsdialogen. Berechtigungen werden über ein zentrales usePermissions-Composable abgefragt, das einen Pinia-Auth-Store konsumiert.

Der größte Hebel für die Qualität einer Admin-Oberfläche liegt in der konsequenten Trennung der Verantwortlichkeiten. Komponenten, die nur rendern, sind einfach zu testen und wiederzuverwenden. Composables, die nur Logik enthalten, sind unabhängig von DOM und Framework testbar. Pinia-Stores, die nur Server-State halten, sind der Single Source of Truth ohne Duplikate. Diese Trennung macht Admin-Oberflächen mit Vue 3 zu einem System, das mit den Anforderungen des Unternehmens wächst statt unter ihnen zu kollabieren.

Admin-Oberflächen mit Vue 3 — Das Wichtigste auf einen Blick

Tabellen-Architektur

useDataTable-Composable für Sortierung, Selektion und API-Calls. Tabellenkomponente rendert nur – keine Logik in Templates.

URL-Synchronisierung

Filter und Paginierung in Query-Params. Zurück-Button stellt Zustand wieder her. Vue Router useRoute/useRouter als reaktive Bridge.

Bulk-Actions

Optimistisches Entfernen, Toast mit Undo (5 Sek.), Rollback bei API-Fehler. Set<id> für O(1)-Selektion.

Berechtigungen

usePermissions-Composable mit can()-Methode, gespeist aus Pinia-Auth-Store. Router-Guards für Route-Ebene.

11. FAQ: Admin-Oberflächen mit Vue 3

1Sortierbare Tabelle mit Vue 3 bauen?
useDataTable-Composable mit Sortierstatus als {column, direction}. Spaltenklick wechselt Richtung. Tabellenkomponente rendert nur, Composable steuert alles.
2Filter in URL speichern?
useRoute/useRouter als Bridge. Filter-State aus Query-Params initialisieren, Änderungen per router.replace zurückschreiben. Bookmarkbar und Zurück-Button-kompatibel.
3Optimistic Updates für Bulk-Delete?
Zeilen sofort aus Tabelle entfernen, API-Call im Hintergrund. Toast mit Undo 5 Sek. Bei Fehler Rollback – Zeilen wiederherstellen.
4Race-Conditions verhindern?
AbortController pro Request erstellen, vorherigen abbrechen. fetch() mit signal. AbortError im Catch ignorieren.
5Offset vs. Cursor-Paginierung?
Offset für direktes Seitenspringen in Admin-Tabellen. Cursor für echtzeit-sensitive Feeds ohne Phantom-Row-Problem.
6Berechtigungen in Vue 3 Admin?
usePermissions-Composable mit can()-Methode. Pinia-Auth-Store als Quelle. Frontend-Checks nur für UX – nie Ersatz für serverseitige Autorisierung.
7Selektion bei großen Tabellen?
Set<id> für O(1)-Lookup. Beim Neuladen leeren. "Alle auswählen" nur aktuelle Seite – nicht alle Datensätze gesamt.
8Pinia-Store-Struktur für Admin-Apps?
Store pro Ressource (Orders, Products). Separater Auth-Store. UI-Store für Sidebar, Theme. Server-State und UI-State nie mischen.
9Composables testen?
Vitest mit gemocktem fetchFn. Kein DOM nötig. Sortierklick → Watch → fetchFn aufgerufen. Isoliert von UI-Schicht testbar.
10Paginierung URL-synchronisieren?
currentPage aus route.query.page. watch(currentPage) → router.replace. watch(route.query.page) → currentPage. Guards gegen zirkuläre Updates.