WebSocket, SSE und Polling im direkten Vergleich
Realtime-Features in Vue.js sind mehr als ein geöffneter WebSocket. Wer WebSocket, Server-Sent Events und Polling nicht bewusst wählt, baut entweder zu viel Infrastruktur auf oder kassiert unnötige Netzwerklast. Dieser Artikel zeigt alle drei Strategien mit sauberen Composables, Lifecycle-Handling und konkreten Einsatzempfehlungen.
Inhaltsverzeichnis
- 1. Realtime in Vue.js — drei Wege, ein Ziel
- 2. Polling: Der einfachste Einstieg in Realtime
- 3. Server-Sent Events: Unidirektionaler Push vom Server
- 4. WebSocket: Bidirektionale Verbindung für komplexe Szenarien
- 5. Realtime-Composables: Abstraktion und Wiederverwendung
- 6. Reconnect-Strategien und Verbindungsstabilität
- 7. Lifecycle-Integration und Memory-Leaks vermeiden
- 8. Realtime-State im Vue-Store verwalten
- 9. WebSocket, SSE und Polling im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Realtime in Vue.js — drei Wege, ein Ziel
Wer in Vue.js Realtime-Daten anzeigen will — sei es ein Live-Dashboard, ein Chat, Benachrichtigungen oder ein Bestellstatus — steht vor derselben Grundentscheidung: Welcher Transportmechanismus passt zur Anforderung? Die drei Kandidaten sind Polling, Server-Sent Events (SSE) und WebSocket. Alle drei liefern Realtime-Daten in Vue-Komponenten, unterscheiden sich aber fundamental in Protokoll, Overhead, Infrastrukturanforderungen und Komplexität.
Das Problem in der Praxis: Viele Teams greifen reflexartig zu WebSocket, weil es nach der robustesten Lösung klingt. Dabei ist WebSocket für rein unidirektionale Server-Push-Szenarien oft die falsche Wahl — SSE ist einfacher zu implementieren, funktioniert über einfache HTTP-Infrastruktur und wird vom Browser nativ reconnected. Und in Szenarien, in denen Daten sich nur selten ändern, ist durchdachtes Polling mit korrektem Backoff vollkommen ausreichend. Dieser Artikel zeigt alle drei Realtime-Strategien in Vue.js im Detail und gibt konkrete Entscheidungshilfen.
2. Polling: Der einfachste Einstieg in Realtime
Polling ist die älteste und konzeptuell einfachste Realtime-Strategie in Vue: Der Client fragt den Server in regelmäßigen Abständen nach neuen Daten. Die scheinbare Einfachheit verbirgt aber typische Fallstricke. Naives Polling mit setInterval ohne Cleanup führt zu Memory Leaks, wenn die Komponente unmountet wird. Das Intervall läuft weiter, schickt HTTP-Anfragen in den Hintergrund und füllt den Speicher mit nicht aufgeräumten Closures — ein klassisches Problem in Single-Page-Applications, das in der Entwicklung nie auffällt, in der Produktion aber zu Abstürzen nach stundenlangem Betrieb führt.
Sauberes Polling in Vue folgt einem klaren Muster: Das Intervall wird in onMounted gestartet und in onUnmounted zwingend gestoppt. Exponentielles Backoff verhindert, dass Fehler im Server zu einem Laststurm durch weiterpollendes Frontend führen. Adaptive Polling — das Intervall verlängern, wenn der Tab nicht sichtbar ist — reduziert die Netzwerklast um bis zu 90 % bei inaktiven Tabs. Das document.visibilityState-Event ist dabei der Schlüssel: visibilitychange schlägt an, sobald der Nutzer den Tab wechselt.
// composables/usePolling.js — Adaptive polling with exponential backoff
import { ref, onMounted, onUnmounted } from 'vue'
export function usePolling(fetchFn, options = {}) {
const {
interval = 5000,
maxInterval = 60000,
backoffFactor = 2,
immediate = true,
} = options
const data = ref(null)
const error = ref(null)
const isLoading = ref(false)
let timerId = null
let currentInterval = interval
let consecutiveErrors = 0
const poll = async () => {
if (isLoading.value) return
isLoading.value = true
try {
data.value = await fetchFn()
error.value = null
consecutiveErrors = 0
currentInterval = interval // Reset on success
} catch (err) {
error.value = err
consecutiveErrors++
// Exponential backoff: double interval up to maxInterval
currentInterval = Math.min(currentInterval * backoffFactor, maxInterval)
} finally {
isLoading.value = false
}
schedule()
}
const schedule = () => {
// Adaptive: poll less frequently when tab is hidden
const delay = document.hidden ? currentInterval * 4 : currentInterval
timerId = setTimeout(poll, delay)
}
onMounted(() => { if (immediate) poll(); else schedule() })
onUnmounted(() => clearTimeout(timerId))
return { data, error, isLoading }
}
3. Server-Sent Events: Unidirektionaler Push vom Server
Server-Sent Events sind ein unterschätzter Standard für Realtime in Vue. Das Protokoll ist denkbar einfach: Der Client öffnet eine HTTP-Verbindung, der Server hält sie offen und sendet Text-Events im Format data: {...}\n\n. Der Browser implementiert automatisches Reconnect mit konfigurierbarem retry:-Feld. SSE funktioniert über HTTP/1.1 und HTTP/2, kommt ohne spezielle Infrastruktur aus und benötigt keinen WebSocket-Upgrade-Handshake. Bei HTTP/2 sind sogar mehrere SSE-Verbindungen über dieselbe TCP-Verbindung multiplext.
Die Einschränkung von SSE: Die Verbindung ist strikt unidirektional — nur Server-zu-Client. Wenn die Anwendung auch Daten vom Client zum Server streamen muss, ist SSE ungeeignet. Für alle anderen Szenarien — Live-Feeds, Fortschrittsanzeigen, Benachrichtigungen, Bestellstatus-Updates — ist SSE die leichtgewichtigere Alternative zu WebSocket. In Vue wird SSE über einen EventSource-Composable abstrahiert, der sauber mit dem Komponentenlebenszyklus verbunden ist und mehrere Event-Typen verwalten kann.
// composables/useSSE.js — Server-Sent Events with typed event handlers
import { ref, onMounted, onUnmounted } from 'vue'
export function useSSE(url, options = {}) {
const { withCredentials = false, events = {} } = options
const isConnected = ref(false)
const lastEvent = ref(null)
const error = ref(null)
let source = null
const connect = () => {
source = new EventSource(url, { withCredentials })
source.onopen = () => { isConnected.value = true; error.value = null }
source.onerror = (e) => {
isConnected.value = false
error.value = e
// Browser automatically reconnects — no manual retry needed
}
// Default message handler
source.onmessage = (e) => {
try { lastEvent.value = JSON.parse(e.data) }
catch { lastEvent.value = e.data }
}
// Register named event listeners
Object.entries(events).forEach(([type, handler]) => {
source.addEventListener(type, (e) => {
try { handler(JSON.parse(e.data)) }
catch { handler(e.data) }
})
})
}
const disconnect = () => {
source?.close()
isConnected.value = false
}
onMounted(connect)
onUnmounted(disconnect)
return { isConnected, lastEvent, error, disconnect, reconnect: connect }
}
4. WebSocket: Bidirektionale Verbindung für komplexe Szenarien
WebSocket in Vue ist die richtige Wahl, wenn bidirektionale Kommunikation in Echtzeit gefragt ist: Chat-Anwendungen, kollaborative Editoren, Multiplayer-Spiele, Echtzeit-Dashboards mit Nutzerinteraktion. Der WebSocket-Handshake upgradet eine HTTP-Verbindung auf ein TCP-Dauerverbindungsprotokoll — danach können Client und Server gleichzeitig Frames senden, ohne dass jedes Paket einen vollständigen HTTP-Request-Response-Zyklus benötigt. Die Latenz ist messbar geringer als bei HTTP-Polling und der Overhead pro Nachricht minimal.
Die Komplexität liegt im Lifecycle-Management. Eine WebSocket-Verbindung in Vue muss beim Unmount der Komponente geschlossen werden, Reconnect-Logik muss implementiert werden, und der Verbindungsstatus muss als reaktiver Zustand modelliert sein. Ein weiterer kritischer Punkt: Wenn mehrere Komponenten denselben WebSocket-Kanal brauchen, sollte die Verbindung nicht in jeder Komponente neu geöffnet werden, sondern in einem zentralen Pinia-Store oder einem Singleton-Composable verwaltet werden. Das verhindert, dass dieselbe WebSocket-URL fünfzehnmal verbunden ist, weil fünfzehn Komponenten dasselbe Composable instanziieren.
// composables/useWebSocket.js — Reactive WebSocket with auto-reconnect
import { ref, shallowRef, onUnmounted } from 'vue'
export function useWebSocket(url, options = {}) {
const { protocols = [], reconnectDelay = 2000, maxReconnects = 10 } = options
const ws = shallowRef(null)
const status = ref('CLOSED') // CONNECTING | OPEN | CLOSING | CLOSED
const lastMessage = ref(null)
const error = ref(null)
let reconnectCount = 0
let reconnectTimer = null
const send = (data) => {
if (ws.value?.readyState === WebSocket.OPEN) {
ws.value.send(typeof data === 'string' ? data : JSON.stringify(data))
}
}
const connect = () => {
if (reconnectCount >= maxReconnects) return
status.value = 'CONNECTING'
const socket = new WebSocket(url, protocols)
socket.onopen = () => {
status.value = 'OPEN'
error.value = null
reconnectCount = 0
}
socket.onmessage = (e) => {
try { lastMessage.value = JSON.parse(e.data) }
catch { lastMessage.value = e.data }
}
socket.onerror = (e) => { error.value = e }
socket.onclose = (e) => {
status.value = 'CLOSED'
if (!e.wasClean && reconnectCount < maxReconnects) {
reconnectCount++
// Exponential backoff for reconnect
reconnectTimer = setTimeout(connect, reconnectDelay * reconnectCount)
}
}
ws.value = socket
}
const disconnect = () => {
clearTimeout(reconnectTimer)
reconnectCount = maxReconnects // Prevent auto-reconnect
ws.value?.close(1000, 'Component unmounted')
}
connect()
onUnmounted(disconnect)
return { status, lastMessage, error, send, disconnect, reconnect: connect }
}
5. Realtime-Composables: Abstraktion und Wiederverwendung
Rohe WebSocket- oder SSE-APIs direkt in Komponenten zu verwenden ist ein Antipattern, das zu dupliziertem Code und schwer testbaren Komponenten führt. Das richtige Muster für Realtime in Vue ist die Kapselung in wiederverwendbare Composables, die den Transport vollständig verstecken. Eine Komponente, die Bestellstatus-Updates empfangen will, sollte nicht wissen, ob die Updates via WebSocket, SSE oder Polling kommen — das ist eine Implementierungsdetail-Entscheidung, die sich ändern kann, ohne eine einzige Komponente anzufassen.
Ein gut designtes Realtime-Composable gibt reaktive Refs zurück — data, isConnected, error — und exportiert Aktionen wie send oder reconnect. Die Verbindungslogik bleibt vollständig im Composable. Wenn das Composable als Singleton implementiert wird — also beim zweiten Aufruf dieselbe Instanz zurückgibt statt eine neue zu erzeugen — können sich mehrere Komponenten denselben Datenstrom teilen, ohne dass mehrere Verbindungen aufgebaut werden. Dieses Muster ist identisch mit dem Singleton-Pattern in Pinia-Stores, nur leichtgewichtiger.
6. Reconnect-Strategien und Verbindungsstabilität
Netzwerkunterbrechungen sind in mobilen Umgebungen die Regel, nicht die Ausnahme. Eine Realtime-Implementierung in Vue, die bei einem kurzen Verbindungsabbruch dauerhaft offline bleibt, ist in der Produktion nicht einsatzfähig. Die Basis-Reconnect-Strategie für WebSocket ist exponentielles Backoff: erste Wiederverbindung nach 2 Sekunden, zweite nach 4, dann 8, 16 — bis zu einem konfigurierbaren Maximum von zum Beispiel 60 Sekunden. Ohne Backoff bombardiert jeder Client mit unterbrochener Verbindung den Server gleichzeitig mit Reconnect-Versuchen, was zu Lastspitzen führt.
Für SSE übernimmt der Browser das automatische Reconnect per Standard. Der Server kann mit dem retry:-Feld im SSE-Stream das Reconnect-Intervall vorgeben. Für Polling ist Backoff im Composable manuell implementiert. Zusätzlich zum Backoff empfiehlt sich das Beobachten von navigator.onLine und online-Event: Sobald die Netzwerkverbindung wiederhergestellt ist, kann sofort reconnected werden, ohne auf den Backoff-Timer zu warten. Das Zusammenspiel von Backoff und Online-Event-Listener macht Reconnect-Strategien in Vue-Anwendungen robust gegenüber mobilen Netzwechseln.
7. Lifecycle-Integration und Memory-Leaks vermeiden
Memory Leaks durch falsch verwaltete Realtime-Verbindungen in Vue sind häufig und schwer zu diagnostizieren. Das Symptom: Die Anwendung wird nach stundenlangem Betrieb langsamer, der Browser-Tab verbraucht stetig mehr Speicher, Netzwerkrequests häufen sich. Die Ursache: Intervall-Timer, EventSource-Objekte oder WebSocket-Instanzen, die nicht aufgeräumt wurden, wenn Komponenten unmountet werden. In Vue 3 ist onUnmounted der korrekte Ort für Cleanup. In Vue 2 war es beforeDestroy.
Ein weiterer subtiler Leak entsteht durch Event-Listener auf window oder document, die in Composables registriert werden — zum Beispiel für visibilitychange oder online. Diese müssen in onUnmounted entfernt werden, sonst akkumulieren sich die Listener bei jeder Komponenteninstanziierung. Das korrekte Pattern: Listener-Referenz in einer Variable speichern, in onMounted registrieren, in onUnmounted mit derselben Referenz entfernen. addEventListener mit einer anonymen Funktion macht das spätere Entfernen unmöglich — ein klassischer Leak, der in Code-Reviews oft übersehen wird.
8. Realtime-State im Vue-Store verwalten
Wenn Realtime-Daten in Vue mehrere Komponenten betreffen — zum Beispiel der Verbindungsstatus in der Navigation, aktuelle Daten in einer Liste und ein Zähler im Header — gehört der State in einen zentralen Pinia-Store, nicht in einen lokalen Composable. Das ermöglicht, dass alle Komponenten auf denselben reaktiven State zugreifen, ohne Prop-Drilling oder Event-Busse. Die WebSocket- oder SSE-Verbindung wird einmal im Store geöffnet und sendet eingehende Nachrichten direkt in den Store-State.
Das Store-Composable-Pattern für Realtime in Vue kombiniert beide Konzepte: Ein Pinia-Store hält den State — aktuelle Daten, Verbindungsstatus, Fehler. Ein Composable verwaltet die Verbindung und schreibt in den Store. Die Initialisierung erfolgt einmalig in der Root-Komponente oder im Vue-App-Plugin, nicht in jeder einzelnen Konsumenten-Komponente. Dieses Muster skaliert von einfachen Benachrichtigungen bis hin zu komplexen kollaborativen Features ohne Architekturänderungen.
9. WebSocket, SSE und Polling im direkten Vergleich
| Kriterium | Polling | SSE | WebSocket |
|---|---|---|---|
| Richtung | Client → Server | Server → Client | Bidirektional |
| Overhead | Hoch (vollst. HTTP) | Gering (HTTP keepalive) | Minimal (Frames) |
| Infrastruktur | Jeder HTTP-Server | Jeder HTTP-Server | WS-fähiger Server nötig |
| Reconnect | Manuell (Backoff) | Browser automatisch | Manuell (Backoff) |
| Latenz | Intervallabhängig | Niedrig | Sehr niedrig |
| Einsatz | Selten ändernde Daten | Feeds, Notifs, Fortschritt | Chat, Kollaboration |
Die Entscheidung zwischen den drei Realtime-Strategien in Vue folgt einer klaren Logik: Wenn Daten sich selten ändern und eine Verzögerung von mehreren Sekunden akzeptabel ist, reicht Polling mit Backoff vollständig aus. Wenn der Server Ereignisse pushen soll und der Client nur lesen muss, ist SSE die einfachere und infrastrukturarme Wahl. Wenn Client und Server gleichzeitig Daten austauschen müssen — Chat, kollaboratives Editieren, Spielzüge — ist WebSocket die einzig sinnvolle Option. Die technische Komplexität nimmt von Polling zu WebSocket zu; die Infrastrukturanforderungen ebenso.
Mironsoft
Vue.js Realtime-Entwicklung, Architekturberatung und Performance-Optimierung
Realtime-Features, die in der Produktion stabil laufen?
Wir implementieren WebSocket, SSE und Polling-Lösungen in Vue.js mit sauberen Composables, Reconnect-Logik, State-Management und vollständigem Lifecycle-Handling für euren Stack.
Architektur-Review
Bestandsanalyse bestehender Realtime-Implementierungen auf Memory Leaks und Reconnect-Lücken
Composable-Entwicklung
Wiederverwendbare, testbare Realtime-Composables für WebSocket, SSE und adaptives Polling
State-Integration
Pinia-Store-Anbindung für anwendungsweite Realtime-Daten ohne Prop-Drilling
10. Zusammenfassung
Realtime in Vue.js ist keine Einheitslösung, sondern eine Entscheidung zwischen drei Transportmechanismen mit unterschiedlichen Kompromissen. Polling eignet sich für selten ändernde Daten mit akzeptabler Verzögerung und braucht nur adaptives Intervall-Management und Backoff. Server-Sent Events sind die eleganteste Lösung für unidirektionalen Server-Push: kein spezieller Infrastrukturaufwand, automatisches Browser-Reconnect, geringe Latenz. WebSocket ist die richtige Wahl nur dort, wo wirklich bidirektionale Echtzeitkommunikation gefragt ist.
In allen drei Szenarien gilt dasselbe Vue-Prinzip: Transportlogik gehört in Composables, nicht in Komponenten. onUnmounted-Cleanup ist kein Optional, sondern Pflicht. Exponentielles Backoff schützt den Server vor Reconnect-Stürmen. Und wer mehrere Komponenten mit denselben Realtime-Daten versorgen muss, zentralisiert den State in einem Pinia-Store, statt mehrere parallele Verbindungen aufzubauen.
Realtime in Vue.js — Das Wichtigste auf einen Blick
Polling
Adaptives Intervall, exponentielles Backoff, document.hidden für inaktive Tabs. Cleanup in onUnmounted ist Pflicht.
SSE
Browser-natives Reconnect, kein WS-Server nötig. Perfekt für Feeds, Benachrichtigungen und Fortschrittsanzeigen.
WebSocket
Bidirektional mit minimalstem Frame-Overhead. Reconnect-Backoff manuell implementieren. Singleton im Pinia-Store für geteilte Verbindungen.
Composables
Transport von Komponenten trennen. Reaktive Refs nach außen exponieren. Lifecycle-Cleanup immer in onUnmounted.