Published 12/5/2025 · 5 min read
Tags: svelte , lifecycle , components
Lesson 10: Component Lifecycle
Components aren’t static. They’re created, updated, and eventually destroyed. Svelte gives you hooks to run code at each stage.
The Lifecycle Functions
Svelte provides four lifecycle functions:
onMount— runs after the component is first renderedonDestroy— runs before the component is removedbeforeUpdate— runs before the DOM updatesafterUpdate— runs after the DOM updates
Plus a utility function:
tick— returns a promise that resolves when pending state changes are applied
onMount
The most commonly used lifecycle function. It runs once, after the component is first rendered to the DOM.
<script>
import { onMount } from 'svelte'
let data = null
onMount(async () => {
const response = await fetch('/api/data')
data = await response.json()
})
</script>
{#if data}
<pre>{JSON.stringify(data, null, 2)}</pre>
{:else}
<p>Loading...</p>
{/if}
Why not just fetch in the script?
The script runs during component initialization, which might happen on the server (with SSR). onMount only runs in the browser, where fetch and DOM APIs are available.
Cleanup:
If you return a function from onMount, it runs when the component is destroyed:
<script>
import { onMount } from 'svelte'
let seconds = 0
onMount(() => {
const interval = setInterval(() => {
seconds++
}, 1000)
// Cleanup function
return () => {
clearInterval(interval)
}
})
</script>
<p>Elapsed: {seconds} seconds</p>
This pattern is essential for preventing memory leaks.
onDestroy
Runs before the component is removed from the DOM.
<script>
import { onDestroy } from 'svelte'
// Subscribe to something
const subscription = eventBus.subscribe('message', handleMessage)
onDestroy(() => {
// Clean up
subscription.unsubscribe()
})
function handleMessage(msg) {
console.log(msg)
}
</script>
You can use either the cleanup return from onMount or a separate onDestroy. They’re equivalent for most cases.
beforeUpdate and afterUpdate
These run before and after DOM updates. They’re less common but useful for specific scenarios.
<script>
import { beforeUpdate, afterUpdate } from 'svelte'
let messages = []
let div
let autoscroll = false
beforeUpdate(() => {
// Check if we're scrolled to the bottom
if (div) {
autoscroll = div.scrollHeight - div.scrollTop === div.clientHeight
}
})
afterUpdate(() => {
// If we were at the bottom, stay at the bottom
if (autoscroll) {
div.scrollTop = div.scrollHeight
}
})
</script>
<div bind:this={div} class="messages">
{#each messages as message}
<p>{message}</p>
{/each}
</div>
This pattern keeps a chat scroll pinned to the bottom as new messages arrive, but only if the user was already at the bottom.
tick
Svelte batches DOM updates for performance. Sometimes you need to wait for the DOM to update before doing something.
<script>
import { tick } from 'svelte'
let text = ''
let input
async function addExclamation() {
text += '!'
// DOM hasn't updated yet
console.log(input.value) // Old value
await tick()
// Now DOM is updated
console.log(input.value) // New value with !
// Select all text
input.select()
}
</script>
<input bind:value={text} bind:this={input} />
<button onclick={addExclamation}>Add !</button>
Without await tick(), input.select() would run before the DOM updated.
Comparing to Vue
Vue’s lifecycle hooks:
<script setup>
import { onMounted, onUnmounted, onBeforeUpdate, onUpdated } from "vue";
onMounted(() => {
console.log("Mounted");
});
onUnmounted(() => {
console.log("Unmounted");
});
</script>
Svelte:
<script>
import { onMount, onDestroy } from 'svelte'
onMount(() => console.log('Mounted'))
onDestroy(() => console.log('Destroyed'))
</script>
Very similar. Vue has nextTick, Svelte has tick.
Common Patterns
Fetching data:
<script>
import { onMount } from 'svelte'
export let userId
let user = null
let loading = true
let error = null
onMount(async () => {
try {
const response = await fetch(`/api/users/${userId}`)
if (!response.ok) throw new Error('Failed to fetch')
user = await response.json()
} catch (e) {
error = e.message
} finally {
loading = false
}
})
</script>
Setting up event listeners:
<script>
import { onMount } from 'svelte'
let windowWidth
onMount(() => {
function handleResize() {
windowWidth = window.innerWidth
}
handleResize() // Initial value
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
})
</script>
<p>Window width: {windowWidth}px</p>
Third-party library integration:
<script>
import { onMount, onDestroy } from 'svelte'
import Chart from 'chart.js/auto'
export let data
let canvas
let chart
onMount(() => {
chart = new Chart(canvas, {
type: 'bar',
data: {
labels: data.map(d => d.label),
datasets: [{
data: data.map(d => d.value)
}]
}
})
})
onDestroy(() => {
if (chart) {
chart.destroy()
}
})
</script>
<canvas bind:this={canvas} />
Lifecycle and SSR
Important: onMount doesn’t run during server-side rendering. This is intentional — the DOM doesn’t exist on the server.
This means you can safely use browser-only APIs inside onMount:
<script>
import { onMount } from 'svelte'
let position = { x: 0, y: 0 }
onMount(() => {
function handleMouseMove(event) {
position = { x: event.clientX, y: event.clientY }
}
window.addEventListener('mousemove', handleMouseMove)
return () => {
window.removeEventListener('mousemove', handleMouseMove)
}
})
</script>
If you try to access window directly in the script (outside onMount), you’ll get errors during SSR.
Multiple Lifecycle Calls
You can call lifecycle functions multiple times:
<script>
import { onMount } from 'svelte'
// Set up first thing
onMount(() => {
console.log('First mount callback')
})
// Set up second thing
onMount(() => {
console.log('Second mount callback')
})
</script>
Both callbacks run in order. This is useful when different parts of your component have their own setup needs.
Key Takeaways
onMountruns once after first render — use for fetching, DOM setup- Return a cleanup function from
onMountto run on destroy onDestroyruns before component removalbeforeUpdateandafterUpdaterun around DOM updatestick()returns a promise that resolves after pending updatesonMountdoesn’t run during SSR — safe for browser APIs- Always clean up subscriptions, intervals, and event listeners
Related Articles
- x402 with SvelteKit: Full-Stack Example
Build a complete SvelteKit application with x402 payments - wallet connection, protected routes, and automatic payment handling.
- Interacting with Programs from Svelte
Build Svelte components that interact with Solana programs - token balances, transfers, and real-time updates.
- Signing Messages and Transactions in the Browser
Learn to sign messages for authentication and build transactions that users approve through their wallet.