Lifecycle

Chapter 9
Lifecycle
onMount and onDestroy — and why $effect often replaces both

A component is created, mounted to the DOM, and eventually destroyed. Svelte provides lifecycle functions for the key moments — but with a twist: in Svelte 5, the $effect rune (Chapter 3) handles many cases that would be lifecycle hooks in other frameworks, because an effect with a cleanup return already covers "run on mount, clean up on destroy."

onMount — After the Component Is in the DOM

<script> import { onMount } from 'svelte'; let users = $state([]); onMount(async () => { const res = await fetch('/api/users'); users = await res.json(); }); </script>

onMount runs a callback once, after the component is first inserted into the DOM — the standard place for an initial data fetch, the equivalent of React's useEffect(() => {...}, []), Vue's onMounted, and Angular's ngOnInit. Unlike the others, onMount is imported from 'svelte' (it's a function, not a rune). By the time it runs, the DOM exists, so it's also safe to measure or access elements here.

Top-level script code runs at "creation" — before mount
Much of what you'd put in onMount elsewhere can just live at the top of <script>: $state, $derived, and plain setup run once when the component is created, before it mounts. Reach for onMount specifically when you need the DOM to exist first (measuring an element, initializing a canvas, a third-party widget) or want to defer work until after the first render — and note onMount does not run during server-side rendering, which makes it the right place for browser-only code.

onDestroy — Cleanup Before Removal

import { onMount, onDestroy } from 'svelte'; let id; onMount(() => { id = setInterval(() => console.log('tick'), 1000); }); onDestroy(() => { clearInterval(id); // stop the timer when the component is removed });

onDestroy runs just before the component is removed — the place for cleanup, mirroring Vue's onUnmounted and Angular's ngOnDestroy. The universal rule applies: anything that "starts" something ongoing (a timer, a listener, a subscription) needs a matching "stop" here, or it leaks.

The Cleaner Way — onMount Returning Cleanup

onMount(() => { const id = setInterval(() => console.log('tick'), 1000); return () => clearInterval(id); // returned function runs on destroy });

A neat shortcut: if onMount returns a function, Svelte calls it on destroy automatically — so setup and teardown live together in one place, without a separate onDestroy. This is exactly React's useEffect cleanup-return pattern, and it keeps the timer's start and stop side by side.

$effect Often Replaces Lifecycle Entirely

<!-- no onMount/onDestroy needed --> $effect(() => { const id = setInterval(() => console.log('tick'), 1000); return () => clearInterval(id); });

Because $effect (Chapter 3) runs after mount and its returned cleanup runs on destroy, an effect frequently does the job of onMount + onDestroy together — and re-runs if its dependencies change, which a one-time onMount doesn't. The practical guidance for Svelte 5: use $effect for reactive setup/teardown (anything that should respond to changing state); use onMount for one-time, mount-only, browser-only setup (DOM measurement, SSR-sensitive code). You'll reach for onDestroy on its own fairly rarely now.

Svelte 4 had more lifecycle hooks — most are now $effect
Svelte 4 also exposed beforeUpdate and afterUpdate hooks (run around each re-render). Svelte 5 deprecated them in favor of $effect (and $effect.pre for "before DOM update"), since reactive effects express the same intent more precisely. onMount and onDestroy remain; beforeUpdate/afterUpdate are the ones to replace with effects in modern code.
Svelte 5ReactVueAngular
top of <script>component bodytop of <script setup>constructor
onMountuseEffect(.., [])onMountedngOnInit
onDestroy / onMount cleanup returncleanup returnonUnmountedngOnDestroy
$effect (+ cleanup)useEffect (reactive)watchEffecteffect()

Coding Challenges

Challenge 1

Build a component that fetches a list from any free public API in onMount, storing it in a $state for display, with a loading state shown until the data arrives.

📄 View solution
Challenge 2

Build a Clock component that starts a setInterval in onMount (updating a time $state every second) and clears it by returning a cleanup function from onMount. Toggle the component with {#if} in a parent to confirm the ticking stops when removed.

📄 View solution
Challenge 3

Build a component that tracks the window's width using a resize listener — implemented two ways for comparison: once with onMount + onDestroy, and once with a single $effect that returns its cleanup. Display the live width.

📄 View solution

Chapter 9 Quick Reference

  • Top-of-<script> code runs at creation, before mount
  • onMount (imported from 'svelte') — runs once after mount; browser-only, skipped during SSR
  • onDestroy — runs before removal; or return a cleanup function from onMount instead
  • $effect with a cleanup return often replaces onMount + onDestroy, and re-runs reactively
  • Guidance: $effect for reactive setup/teardown; onMount for one-time, mount-only, browser-only work
  • Svelte 4's beforeUpdate/afterUpdate are replaced by $effect / $effect.pre
  • Next chapter: shared reactive logic in .svelte.js modules