Lifecycle
$effect covers most lifecycle needs
Section titled “$effect covers most lifecycle needs”In Svelte 4, you reached for lifecycle hooks (onMount, beforeUpdate, afterUpdate) for anything involving the DOM or timing. In Svelte 5, $effect covers most of that: it runs after the DOM updates, re-runs when its dependencies change, and its returned cleanup function handles teardown.
<script> let count = $state(0);
$effect(() => { document.title = `Count: ${count}`; // runs after mount and on every change return () => { document.title = 'App'; }; // cleanup on destroy / before re-run });</script>So before reaching for a lifecycle function, ask whether an effect fits. Often it does.
onMount and onDestroy
Section titled “onMount and onDestroy”Two lifecycle functions remain genuinely useful:
onMount(fn)runs once after the component is first rendered to the DOM, only in the browser (never during SSR). It’s the right place for browser-only setup: measuring the DOM, initializing a canvas or map library, starting an interval. Iffnreturns a function, it’s called on destroy.onDestroy(fn)runs when the component is removed — for cleanup that isn’t tied to a specific effect (unsubscribing, clearing timers).
<script> import { onMount, onDestroy } from 'svelte';
onMount(() => { const chart = new Chart(canvasEl); // browser-only library return () => chart.destroy(); // cleanup on unmount });
onDestroy(() => console.log('gone'));</script>The key distinction from $effect: onMount runs exactly once and only in the browser, which is precisely what you want for one-time, browser-only initialization. An effect that reads no reactive state also runs once, but onMount states that intent clearly and guarantees browser-only execution.
flowchart LR create["component created"] --> mount["onMount (browser, once)"] mount --> effects["$effect runs after DOM updates"] effects --> effects effects --> destroy["onDestroy + effect cleanups"]
tick: waiting for the DOM to update
Section titled “tick: waiting for the DOM to update”Svelte batches DOM updates. When you change state and need the DOM to reflect it before your next line runs (to measure an element, or focus a freshly-shown input), await tick() resolves once pending changes are applied.
<script> import { tick } from 'svelte';
async function addAndScroll() { items.push(newItem); await tick(); // wait for the new row to be in the DOM list.scrollTop = list.scrollHeight; }</script>