Wer aus Svelte 4 kommt, kennt vielleicht beforeUpdate und afterUpdate — Hooks, die bei jeder Aktualisierung der Komponente liefen. In Svelte 5 mit Runes gibt es diese Hooks im neuen Stil nicht mehr. Stattdessen gilt: Was du früher mit beforeUpdate gemacht hast, machst du jetzt mit $effect.pre. Was du mit afterUpdate gemacht hast, machst du mit $effect. Der Unterschied: Effects laufen nur, wenn sich tatsächlich relevante Werte geändert haben — nicht auf jede einzelne Aktualisierung.

Was waren beforeUpdate und afterUpdate?

In Svelte 4 gab es zwei Lifecycle-Hooks rund um DOM-Updates:

svelte Svelte 4 (alt)
<script>
    import { beforeUpdate, afterUpdate } from 'svelte';

    beforeUpdate(() => {
        console.log('vor jedem DOM-Update');
    });

    afterUpdate(() => {
        console.log('nach jedem DOM-Update');
    });
</script>

Das Problem dabei: Sie liefen bei jeder Aktualisierung der Komponente — egal, was sich geändert hatte. Hat sich nur eine völlig unbedeutende Variable geändert, lief der Hook trotzdem. Wer in afterUpdate z. B. zum unteren Ende einer Chat-Liste gescrollt ist, hat das auch dann gemacht, wenn sich nur das Theme geändert hatte.

Die neue Welt: $effect und $effect.pre

In Svelte 5 ersetzt du:

Svelte 4Svelte 5Wann läuft es?
beforeUpdate$effect.preVor dem DOM-Update
afterUpdate$effectNach dem DOM-Update

Der entscheidende Unterschied: Beide Runes verfolgen automatisch ihre Abhängigkeiten. Sie laufen nur, wenn sich tatsächlich ein darin gelesener reaktiver Wert geändert hat. Das macht den Code feinkörniger und vermeidet unnötige Arbeit.

Klassisches Beispiel: Chat-Auto-Scroll

Eine Chat-Komponente soll automatisch zum unteren Ende scrollen, wenn neue Nachrichten kommen — aber nicht jedes Mal, wenn sich irgendein anderer State der Komponente ändert.

So sah es in Svelte 4 aus

svelte Svelte 4 (alt)
<script>
    import { afterUpdate } from 'svelte';

    export let messages = [];
    export let theme = 'light';

    let container;

    afterUpdate(() => {
        container.scrollTop = container.scrollHeight;
    });
</script>

<div bind:this={container}>
    {#each messages as msg}<p>{msg}</p>{/each}
</div>

Problem: Auch ein Wechsel von theme löste einen Scroll aus, obwohl das gar nichts mit Nachrichten zu tun hat.

So löst Svelte 5 das eleganter

svelte Svelte 5
<script>
    let { messages, theme } = $props();
    let container;

    $effect(() => {
        // automatisches Tracking: nur messages.length wird beobachtet
        messages.length;
        container.scrollTop = container.scrollHeight;
    });
</script>

<div bind:this={container}>
    {#each messages as msg}<p>{msg}</p>{/each}
</div>

Der Effect verfolgt automatisch, dass er messages.length liest. Er läuft also, wenn sich messages ändert — aber nicht, wenn theme wechselt. Genau das Verhalten, das man eigentlich wollte.

Wann $effect.pre?

$effect.pre läuft vor dem DOM-Update — also wenn die State-Werte schon neu sind, das DOM aber noch alt. Das ist wichtig, wenn du vor der Aktualisierung etwas am DOM messen oder festhalten willst, um es danach zu vergleichen.

Klassischer Anwendungsfall: „Soll das Auto-Scroll noch laufen?” Du willst wissen, ob der Nutzer vor dem Update am unteren Ende der Liste war:

svelte Auto-Scroll mit Pre-Check
<script>
    let { messages } = $props();
    let container;
    let wasAtBottom = $state(true);

    $effect.pre(() => {
        // läuft vor dem DOM-Update
        messages.length; // Tracking
        if (!container) return;

        const distance =
            container.scrollHeight -
            container.scrollTop -
            container.clientHeight;
        wasAtBottom = distance < 20;
    });

    $effect(() => {
        // läuft nach dem DOM-Update
        messages.length; // Tracking
        if (wasAtBottom) {
            container.scrollTop = container.scrollHeight;
        }
    });
</script>

<div bind:this={container}>
    {#each messages as msg}<p>{msg}</p>{/each}
</div>

Schritt für Schritt:

  1. $effect.pre läuft vor dem DOM-Update. Wir prüfen den Scroll-Stand und merken uns in wasAtBottom, ob der Nutzer am Ende war.
  2. $effect läuft nach dem DOM-Update. Wenn der Nutzer vorher am Ende war, scrollen wir wieder ans Ende. Wenn nicht (weil er nach oben gescrollt hat), bleibt seine Scroll-Position erhalten.

So fühlt sich der Chat richtig an — Auto-Scroll bei neuen Nachrichten, aber nur wenn der Nutzer ihn nicht beim Lesen oben unterbrochen hat.

Der wichtigste Unterschied: gezielte vs. globale Reaktion

Mit beforeUpdate/afterUpdate hast du auf jedes Komponenten-Update reagiert. Mit $effect und $effect.pre reagierst du gezielt auf bestimmte Werte.

Konsequenz für die Codestruktur: Du schreibst meist mehrere kleine Effects, jeder mit einer spezifischen Aufgabe — statt eines großen Hooks mit if-Verzweigungen darin.

svelte Mehrere Effects, jeder mit klarer Aufgabe
<script>
    let { userId, theme } = $props();

    // Reagiert auf userId
    $effect(() => {
        document.body.dataset.userId = userId;
    });

    // Reagiert auf theme
    $effect(() => {
        document.documentElement.dataset.theme = theme;
    });
</script>

Diese Aufteilung war in Svelte 4 mit afterUpdate schwer sauber zu machen — da musste man im Hook nachfragen „was hat sich geändert?”. Heute ist das automatisch.

Cleanup in $effect

Wie onMount kann auch $effect eine Cleanup-Funktion zurückgeben. Sie läuft, bevor der Effect erneut ausgeführt wird — und beim Unmount.

svelte Cleanup pro Effect-Lauf
<script>
    let { delay } = $props();

    $effect(() => {
        const id = setTimeout(() => {
            console.log('Tick');
        }, delay);

        return () => clearTimeout(id);
    });
</script>

Wenn delay sich ändert, läuft erst der Cleanup (alter Timer wird abgeräumt), dann der Effect mit neuem Wert. Beim Unmount läuft nur der Cleanup. Sehr kompakt, sehr sauber.

Wann doch onMount statt $effect?

$effect ist mächtig, aber nicht immer die richtige Wahl. Faustregel:

  • „Etwas einmalig beim Start initialisieren” -> onMount. Effekt: Klar als „läuft genau einmal” erkennbar.
  • „Reaktion auf Änderungen über die Lebenszeit der Komponente” -> $effect. Mehrfaches Laufen ist normal.

Beispiel: Eine Chart-Library zu instanziieren ist eine onMount-Aufgabe. Das Datenset zu aktualisieren, sobald sich die data-Prop ändert, ist eine $effect-Aufgabe.

Häufige Stolperfallen

$effect statt $derived für Berechnungen. Wenn du in $effect einen reaktiven Wert schreibst, der aus anderen reaktiven Werten berechnet wird, gehört das nach $derived. Effects sind für Side Effects, nicht für Ableitungen.

Tracking nicht verstehen. $effect verfolgt nur Werte, die synchron im Body gelesen werden. Werte hinter await, in einem späteren Callback oder in einer separaten Funktion werden nicht automatisch zur Abhängigkeit. Wenn der Effect nicht läuft wie erwartet, ist das oft die Ursache.

Effects in falscher Reihenfolge erwarten. Mehrere Effects laufen in der Reihenfolge ihrer Definition, aber alle innerhalb derselben Microtask. Wenn du auf einen anderen Effect angewiesen bist, ist das oft ein Zeichen für ein $derived-Versteck.

$effect.pre nutzen, wo $effect reicht. $effect.pre ist seltener nützlich. Erst greifen, wenn du wirklich vor dem DOM-Update etwas messen oder vorbereiten musst.

Cleanup vergessen. Bei Timern, Listenern, Subscriptions: immer eine Cleanup-Funktion zurückgeben. Sonst hast du Memory-Leaks bei jedem Re-Run des Effects.

Weiterführende Ressourcen

Externe Quellen

Verwandte Artikel

/ Weiter

Zurück zu Lifecycle

Zur Übersicht