onDestroy ist das Gegenstück zu onMount: Sie läuft, kurz bevor die Komponente aus dem DOM entfernt wird. Typische Aufgaben sind das Beenden von Timern, Listenern, WebSocket-Verbindungen oder Subscriptions — also alles, was sonst nach dem Verschwinden der Komponente weiterlaufen würde. Eine Besonderheit: Im Gegensatz zu onMount läuft onDestroy auch beim Server-Side-Rendering.
Was ist onDestroy?
Wenn eine Komponente aus dem DOM verschwindet — weil ein {#if} falsch wird, der Nutzer auf eine andere Seite navigiert oder die App beendet wird — bekommt sie eine letzte Chance, aufzuräumen.
<script>
import { onDestroy } from 'svelte';
onDestroy(() => {
console.log('Komponente wird gleich entfernt');
});
</script>
<p>Hallo</p>Wichtig zu wissen:
- Die Funktion wird kurz vor dem tatsächlichen Entfernen aufgerufen.
onDestroyläuft auch beim Server-Side-Rendering (im Gegensatz zuonMount). Das ist ein Unterschied, den man bei serverseitigem Code im Kopf behalten muss.- Es darf — wie
onMount— nur im Top-Level des<script>stehen.
onDestroy vs. Cleanup-Rückgabe in onMount
Eine berechtigte Frage: Warum gibt es zwei Wege, denselben Job zu erledigen?
<script>
import { onMount } from 'svelte';
onMount(() => {
const id = setInterval(() => { /* … */ }, 1000);
return () => clearInterval(id);
});
</script><script>
import { onMount, onDestroy } from 'svelte';
let id;
onMount(() => {
id = setInterval(() => { /* … */ }, 1000);
});
onDestroy(() => {
clearInterval(id);
});
</script>Beides funktioniert. Empfehlung:
- Wenn Setup und Cleanup eng zusammengehören ->
onMountmit Rückgabefunktion. Lokal, kompakt, lesbar. - Wenn Cleanup unabhängig vom Setup ist oder auch beim SSR laufen soll ->
onDestroy.
In den meisten Fällen ist Variante A ergonomischer.
Praxis-Beispiel: Store-Subscription
Eine Komponente abonniert einen Svelte-Store auf eine Art, die das automatische $store-Pattern nicht abdeckt — z. B. mit eigener Filter-Logik. Sie muss die Subscription wieder beenden, sonst läuft der Listener weiter:
<script>
import { onDestroy } from 'svelte';
import { messages } from '$lib/stores/messages';
let count = $state(0);
const unsubscribe = messages.subscribe((all) => {
count = all.filter((m) => m.unread).length;
});
onDestroy(unsubscribe);
</script>
<p>Ungelesen: {count}</p>messages.subscribe(...) gibt selbst eine Funktion zurück, die die Subscription beendet. Wir reichen sie direkt an onDestroy weiter — kompakt und klar.
Praxis-Beispiel: WebSocket schließen
<script>
import { onMount, onDestroy } from 'svelte';
let messages = $state([]);
let socket;
onMount(() => {
socket = new WebSocket('wss://example.com/feed');
socket.onmessage = (event) => {
messages.push(JSON.parse(event.data));
};
});
onDestroy(() => {
socket?.close();
});
</script>
<ul>
{#each messages as msg (msg.id)}
<li>{msg.text}</li>
{/each}
</ul>Hier zeigt sich der Vorteil von onDestroy als eigene Funktion: Wir öffnen den Socket im onMount, halten ihn in einer Modul-Variable, und schließen ihn klar beim Unmount.
SSR-Unterschied im Detail
Beim Server-Side-Rendering wird die Komponente einmal gerendert, das HTML zur Antwort geschickt, und die Komponente am Server „zerstört”. onDestroy läuft dabei. onMount nicht.
Das ist relevant, wenn dein onDestroy-Code Browser-spezifische APIs verwendet:
<script>
import { onDestroy } from 'svelte';
onDestroy(() => {
window.removeEventListener('resize', handleResize); // crasht auf Server
});
</script><script>
import { onMount, onDestroy } from 'svelte';
function handleResize() { /* … */ }
onMount(() => {
window.addEventListener('resize', handleResize);
// Cleanup-Variante: läuft nur im Browser
return () => window.removeEventListener('resize', handleResize);
});
</script>Faustregel: Wenn Cleanup-Code Browser-APIs braucht, gehört er in die Rückgabefunktion von onMount. Dann läuft er nicht auf dem Server.
Mehrere onDestroy-Aufrufe
Du kannst onDestroy mehrfach in derselben Komponente aufrufen — jeder Callback wird beim Unmount ausgeführt:
<script>
import { onDestroy } from 'svelte';
import { logger } from '$lib/logger';
const unsubscribeA = storeA.subscribe(/* … */);
const unsubscribeB = storeB.subscribe(/* … */);
onDestroy(unsubscribeA);
onDestroy(unsubscribeB);
onDestroy(() => logger.flush());
</script>Die Reihenfolge der Aufrufe entspricht der Reihenfolge, in der onDestroy im Code steht.
Häufige Stolperfallen
onDestroy in Bedingung.
Wie onMount: Aufruf nur im Top-Level. Bedingungen kommen in den Callback.
Auf nicht-existente Variablen zugreifen.
Wenn socket nur im onMount zugewiesen wird, kann er beim ersten SSR-Durchlauf in onDestroy undefined sein. Mit Optional-Chaining (socket?.close()) absichern.
Unsicheres unsubscribe aufrufen.
Manche Store-Bibliotheken liefern bei Subscriptions, die nicht zustande kommen, kein Unsubscribe. if (unsubscribe) onDestroy(unsubscribe); ist defensiver.
Erwartung: Liefert Daten zurück.
onDestroy gibt nichts zurück (keine Cleanup-Cleanup). Was du in onDestroy startest, läuft danach nicht weiter — das ist der ganze Punkt.
Annahme: Läuft beim Tab-Schließen.
onDestroy läuft, wenn die Komponente aus der App entfernt wird — nicht zwingend, wenn der Browser-Tab geschlossen wird. Für Tab-Schließen-Listener nutzt man window.beforeunload (mit Vorbehalten zu seiner Verlässlichkeit).