Skip to main content
In Svelte 5, the component lifecycle consists of two main parts: creation and destruction. Everything in between is handled by reactive effects that update specific parts of the UI when state changes.

Lifecycle Overview

A component’s lifecycle:
  1. Creation - Component is instantiated and mounted to the DOM
  2. Updates - Reactive effects run when dependencies change
  3. Destruction - Component is unmounted and cleanup occurs
1
Component Creation
2
When a component is created:
3
  • The <script> block executes
  • State is initialized
  • Props are received
  • Effects are scheduled
  • DOM is created and mounted
  • onMount callbacks run
  • 4
    Reactive Updates
    5
    During the component’s lifetime:
    6
  • $effect runs when dependencies change
  • $derived values recompute automatically
  • DOM updates are batched and applied
  • No “before update” or “after update” hooks
  • 7
    Component Destruction
    8
    When a component is destroyed:
    9
  • Effect cleanup functions run
  • onDestroy callbacks execute
  • DOM is removed
  • Memory is freed
  • onMount

    The onMount function schedules a callback to run when the component is mounted to the DOM:
    <script>
      import { onMount } from 'svelte';
      
      onMount(() => {
        console.log('Component has mounted');
      });
    </script>
    

    Key Characteristics

    • Runs once per component instance
    • Executes after the component is mounted to the DOM
    • Does not run during server-side rendering
    • Can be called from external modules during initialization

    Cleanup with onMount

    Return a function from onMount to run cleanup when the component unmounts:
    <script>
      import { onMount } from 'svelte';
      
      onMount(() => {
        const interval = setInterval(() => {
          console.log('beep');
        }, 1000);
        
        // Cleanup function
        return () => clearInterval(interval);
      });
    </script>
    
    Note: This only works with synchronous functions. Async functions always return a Promise.

    Common Use Cases

    <script>
      import { onMount } from 'svelte';
      
      let data = $state(null);
      
      onMount(async () => {
        const response = await fetch('/api/data');
        data = await response.json();
      });
    </script>
    
    {#if data}
      <div>{data.title}</div>
    {/if}
    

    onDestroy

    Schedules a callback to run immediately before the component is unmounted:
    <script>
      import { onDestroy } from 'svelte';
      
      onDestroy(() => {
        console.log('Component is being destroyed');
      });
    </script>
    

    Server-Side Rendering

    onDestroy is the only lifecycle hook that runs during server-side rendering:
    <script>
      import { onDestroy } from 'svelte';
      
      const cleanup = () => {
        // This runs on both client and server
        console.log('Cleaning up resources');
      };
      
      onDestroy(cleanup);
    </script>
    

    Effects for Reactivity

    Use $effect for side effects that should run when dependencies change:
    <script>
      let size = $state(50);
      let color = $state('#ff3e00');
      let canvas;
      
      $effect(() => {
        const context = canvas.getContext('2d');
        context.clearRect(0, 0, canvas.width, canvas.height);
        
        // Reruns when color or size change
        context.fillStyle = color;
        context.fillRect(0, 0, size, size);
      });
    </script>
    
    <canvas bind:this={canvas} width="100" height="100"></canvas>
    

    Effect Timing

    Effects run:
    • After the component is mounted
    • In a microtask after state changes
    • Updates are batched for performance
    • After DOM updates are applied

    Effect Cleanup

    Return a cleanup function to run when the effect re-runs or the component is destroyed:
    <script>
      let count = $state(0);
      let milliseconds = $state(1000);
      
      $effect(() => {
        const interval = setInterval(() => {
          count += 1;
        }, milliseconds);
        
        return () => {
          clearInterval(interval);
        };
      });
    </script>
    
    <h1>{count}</h1>
    <button onclick={() => milliseconds *= 2}>slower</button>
    <button onclick={() => milliseconds /= 2}>faster</button>
    

    Pre-DOM Update Effects

    Use $effect.pre to run code before the DOM updates:
    <script>
      import { tick } from 'svelte';
      
      let div = $state();
      let messages = $state([]);
      
      $effect.pre(() => {
        if (!div) return;
        
        messages.length; // Track dependency
        
        // Autoscroll when new messages are added
        if (div.offsetHeight + div.scrollTop > div.scrollHeight - 20) {
          tick().then(() => {
            div.scrollTo(0, div.scrollHeight);
          });
        }
      });
    </script>
    
    <div bind:this={div}>
      {#each messages as message}
        <p>{message}</p>
      {/each}
    </div>
    

    tick() Function

    Use tick to wait for pending DOM updates:
    <script>
      import { tick } from 'svelte';
      
      let items = $state([1, 2, 3]);
      
      async function addItem() {
        items.push(items.length + 1);
        
        // Wait for DOM to update
        await tick();
        
        // Now DOM reflects the new state
        console.log('Item added and rendered');
      }
    </script>
    
    <button onclick={addItem}>Add Item</button>
    <ul>
      {#each items as item}
        <li>{item}</li>
      {/each}
    </ul>
    

    Deprecated: beforeUpdate/afterUpdate

    These hooks from Svelte 4 are deprecated. Use $effect.pre and $effect instead:
    <script>
      // OLD (Svelte 4)
      // import { beforeUpdate, afterUpdate } from 'svelte';
      // beforeUpdate(() => { /* ... */ });
      // afterUpdate(() => { /* ... */ });
      
      // NEW (Svelte 5)
      $effect.pre(() => {
        console.log('before update');
      });
      
      $effect(() => {
        console.log('after update');
      });
    </script>
    

    Advanced: $effect.root

    Create effects outside component initialization:
    <script>
      const destroy = $effect.root(() => {
        $effect(() => {
          console.log('This effect runs independently');
        });
        
        return () => {
          console.log('Cleanup');
        };
      });
      
      // Later...
      // destroy();
    </script>
    

    Lifecycle Best Practices

    1. Use onMount for initialization - Fetch data, set up subscriptions, initialize libraries
    2. Use onDestroy for cleanup - Clear timers, unsubscribe, remove event listeners
    3. Prefer $effect for reactivity - Let Svelte track dependencies automatically
    4. Use $effect.pre sparingly - Only when you need pre-DOM-update timing
    5. Avoid state updates in effects - Use $derived instead when possible
    <script>
      import { onMount, onDestroy } from 'svelte';
      
      let ws;
      let messages = $state([]);
      
      onMount(() => {
        ws = new WebSocket('ws://localhost:8080');
        
        ws.onmessage = (event) => {
          messages.push(event.data);
        };
      });
      
      onDestroy(() => {
        if (ws) {
          ws.close();
        }
      });
    </script>
    

    Effect Dependencies

    Effects automatically track dependencies:
    <script>
      let count = $state(0);
      let name = $state('world');
      
      $effect(() => {
        // Only reruns when count changes
        console.log(`Count is ${count}`);
      });
      
      $effect(() => {
        // Reruns when either count or name changes
        console.log(`${name}: ${count}`);
      });
    </script>
    
    Dependencies are tracked synchronously - values read after await or in setTimeout are not tracked.