Svelte State and Component Lifecycle

Svelte 5 made reactivity explicit with runes: $state declares reactive state, $derived computes values from it, $effect runs side effects when what it reads changes, and $props receives inputs. The compiler turns these into fine-grained updates that touch only the DOM nodes that depend on changed values — no virtual DOM diffing.

Explicit does not mean foolproof. Effects that derive state cause extra renders and loops, effects without cleanup leak timers and requests, browser-only code breaks server rendering, module-level state leaks between users on the server, and errors in asynchronous code bypass error boundaries. This page walks through the rules that keep Svelte components predictable, built around a small search component that loads data, cancels stale requests, and handles failure — and a test that verifies it.

TL;DR

Quick Example

A search component with derived values, a debounced request, cancellation, and error state:

A component test with Vitest and Testing Library:

Core Concepts

State

Reassigning or mutating $state updates only what depends on it. Destructuring a state object copies its current values, which are no longer reactive — read through the object instead.

Effects

$effect runs after the component mounts and after DOM updates, whenever state it read synchronously changes. Values read inside await, setTimeout, or callbacks are not tracked. The returned function runs before the next execution and on destroy — the place to cancel requests, clear timers, and remove listeners. $effect.pre runs before DOM updates, for cases such as preserving scroll position.

Effects are for synchronizing with the outside world: network requests, subscriptions, canvas drawing, analytics. Computing one piece of state from another belongs in $derived; writing state from an effect that also reads it can loop.

Props and Bindings

$props() returns the component's inputs, with defaults through destructuring. Props flow down; to let a parent bind to a value (bind:value), mark it with $bindable(). Prefer callbacks (onchange) for most child-to-parent communication, so data flow stays traceable.

Lifecycle Functions

Server Rendering and Hydration

With SvelteKit, components render on the server and hydrate in the browser. Effects and onMount do not run on the server, so browser APIs belong there — or behind SvelteKit's browser flag. Markup must match between server and client: values that differ (random IDs, Date.now(), locale-dependent formatting) cause hydration mismatches. Load data in SvelteKit load functions rather than in effects, so it is available on the server and not fetched twice. See SvelteKit.

Shared State

State in .svelte.js or .svelte.ts modules can use runes, but a module cannot export a reassignable $state variable directly — export an object whose properties are state, or getter functions. On the server, module-level state is shared by every request, which can leak one user's data into another's page. Put per-request or per-user state in context (setContext/getContext) created in a layout, or in SvelteKit's load data. See State Management.

Asynchronous Errors

Error boundaries do not catch errors in event handlers, setTimeout, or other asynchronous callbacks; handle those in code and reflect them in state. See Loading, Empty, and Error States.

Best Practices

Derive, Don't Synchronize

Use $derived for anything computed from state; reserve $effect for side effects outside Svelte.

Always Clean Up Effects

Return cleanup functions that abort requests, clear timers, and unsubscribe, so rapid changes and unmounts cannot leave stale work running.

Keep Browser Code Out of SSR Paths

Access browser APIs in effects, onMount, or behind the browser flag, and keep server and client markup identical.

Test Behavior, Not Internals

Render components, interact like a user, and assert visible output and network calls, mocking fetch and controlling timers.

Common Mistakes

Effects That Copy State

Missing Cleanup for Requests

Module-Level State on the Server

FAQ

What are runes in Svelte?

Compiler instructions such as $state, $derived, $effect, and $props that declare reactive state, computed values, side effects, and component inputs explicitly in Svelte 5.

When should I use $effect versus $derived?

Use $derived to compute a value from other state. Use $effect only to synchronize with something outside Svelte — network requests, timers, DOM APIs, or third-party libraries.

Does $effect run during server-side rendering?

No. Effects and onMount run only in the browser, after the component mounts. Component scripts themselves run on the server during SSR.

How do I share state between Svelte components?

Pass props and callbacks for nearby components; use objects with $state properties in .svelte.js modules for client-only global state; and use context for state that must be scoped to a component tree or a server request.

Do Svelte error boundaries catch errors in async code?

<svelte:boundary> catches errors thrown during rendering and in effects within it. Errors in event handlers, timers, and other asynchronous callbacks must be caught in code and turned into state.

Related Topics

References