SuperAPI AI Directory

svelte-runtime

Frontend & websites

View repository

Updated 10-16 05:48

About this skill

Svelte Runtime Reference (Svelte 5)

This skill covers Svelte 5's runtime APIs, including cross-component state (Stores/Context), lifecycle, imperative APIs, SSR, and testing.

When to use this skill

Use this skill when the user needs to share state across components, handle component lifecycles, use mount/render and other imperative APIs, or write Vitest component tests.

Critical: Stores (svelte/store)

A Store is an older way to share reactive state across components (Svelte 5 recommends using the .svelte.js object in $state instead).

writable

import { writable } from 'svelte/store';
const count = writable(0);

count.subscribe(v => console.log(v)); // subscribe count.set(1); // set value count.update(n => n + 1); // update


### readable(不可从外部设置)

```js
import { readable } from 'svelte/store';
const time = readable(new Date(), (set) => {
  const interval = setInterval(() => set(new Date()), 1000);
  return () => clearInterval(interval);
});

derived

import { derived } from 'svelte/store';
const doubled = derived(count, $c => $c * 2);
const combined = derived([a, b], ([$a, $b]) => $a + $b);

readonly

import { readonly, writable } from 'svelte/store';
const readOnlyStore = readonly(writableStore);

Using in components

<script>
  import { count } from './store.js';
  console.log($count); // $ 前缀订阅
  $count = 2;          // 自动调用 .set()
</script>

A Store must be declared at the top level of a component; do not add a $ prefix before the variable (that is $count, not count)。

Svelte 5 recommended alternatives:

// state.svelte.js
export const userState = $state({ name: '', count: 0 });
// 其他组件直接 import

Critical: Context (svelte)

Pass data across component levels without prop-drilling through layers of props:

setContext / getContext

// Parent.svelte
<script>
  import { setContext } from 'svelte';
  setContext('key', 'hello');
</script>

// Child.svelte
<script>
  import { getContext } from 'svelte';
  const msg = getContext('key');
</script>

createContext (recommended, type-safe)

// context.ts
import { createContext } from 'svelte';
export const [getUserContext, setUserContext] = createContext<User>();
// Parent.svelte
<script> setUserContext({ name: 'Ada' }); </script>

// Child.svelte
<script> const user = getUserContext(); </script>

Reactive Context

Store the $state object in Context:

setContext('counter', counterState);
// ❌ 不要重新赋值整个对象
// counterState = { count: 0 }
// ✅ 直接修改属性
counterState.count = 0;

Critical: Lifecycle Hooks

Mental model: Svelte 5's lifecycle has only two phases — creation and destruction. The "state update" in between is handled by effect , because the smallest unit of reactivity is the effect, not the component. So there is no beforeUpdate/afterUpdate hook.

onMount

Runs after the component is mounted to the DOM (does not run during SSR):

<script>
  import { onMount } from 'svelte';
  onMount(() => {
    console.log('mounted');
    return () => console.log('unmounted'); // 返回清理函数
  });
</script>

The returned function is called when the component is unmounted; if an async function is passed in, the cleanup function will not be called (async functions always return Promise). When you need an async task plus cleanup, wrap it in an IIFE, and the outer arrow function synchronously returns the cleanup function.

onDestroy

Runs before the component is unmounted (also runs during SSR):

<script>
  import { onDestroy } from 'svelte';
  onDestroy(() => cleanup());
</script>

tick

Wait for DOM updates to complete before continuing:

<script>
  import { tick } from 'svelte';
  $effect.pre(() => {
    // DOM 更新前
    tick().then(() => {
      // DOM 已更新完成
    });
  });
</script>

Lifecycle coordination with mount/unmount/hydrate

Phase mount() hydrate() render() (SSR) unmount()
Synchronous: component script, state, template ✓ ✓ ✓ —
Synchronous: insert into DOM / output string ✓ replace target ✓ reuse SSR DOM ✓ return head+body —
Microtask:$effect / onMount trigger ✓ ✓ ✗ —
Synchronous: cleanup effects + onDestroy — — ✓ (after render completes) ✓
Asynchronous: transitions (outro) — — — outro:true wait during

mount() When returns, effects and onMount have not run yet; to make them run immediately, flushSync()。hydrate() Same for. tick() and flushSync() are no-ops on the server (there is no reactive scheduling).

Critical: Imperative Component API

mount (client-side mount)

import { mount } from 'svelte';
import App from './App.svelte';

const app = mount(App, {
  target: document.querySelector('#app'),
  props: { name: 'world' }
});

unmount (unmount)

import { mount, unmount } from 'svelte';
unmount(app, { outro: true }); // 播放过渡后卸载

render (server-side rendering)

import { render } from 'svelte/server';
const { head, body } = await render(App, { props: { data } });

hydrate (client-side hydration)

Reuse SSR HTML and activate it into interactive:

import { hydrate } from 'svelte';
const app = hydrate(App, { target: document.querySelector('#app') });

Critical: Hydratable Data

Serialize data during SSR and reuse it during client hydration (avoid duplicate requests):

<script>
  import { hydratable } from 'svelte';
  const user = await hydratable('user', () => getUser());
</script>
<h1>{user.name}</h1>

CSP support

hydratable() will embed an inline render() in the head returned by <script>. CSP blocks it by default; you need to allow it with a nonce or hash:

// 动态 SSR:每请求一个 nonce
const nonce = crypto.randomUUID();
const { head, body } = await render(App, {
  csp: { nonce }
});
// 响应头同步加上:
// Content-Security-Policy: script-src 'self' 'nonce-${nonce}'
// 静态 SSG:使用 hash
const { head, body, hashes } = await render(App, {
  csp: { hash: true }
});
// Content-Security-Policy: script-src ${hashes.script.map(h => `'${h}'`).join(' ')}

Important:

  • Not used hydratable() when render() does not output inline <script>, CSP strict mode will not block anything.
  • inline event handlers (<button onclick={...}>) are not <script> tags and do not need a nonce.
  • The nonce mode is recommended; hash mode will conflict with streaming SSR in the future.
  • See references/csp-reference.md。

Critical: Best Practices

$state

// ✅ 只对需要响应式的变量用 $state
let count = $state(0);

// ✅ 大数组/不可变数据用 $state.raw
let apiData = $state.raw([]);
apiData = await fetchData(); // 重新赋值触发更新

$derived

// ✅ 派生值用 $derived
let doubled = $derived(count * 2);

// ❌ 不要用 $effect 同步派生
let square;
$effect(() => { square = num * num; }); // ❌
let square = $derived(num * num);         // ✅

$effect

// ✅ 仅用于副作用
$effect(() => { myChart.resize(); });

// ❌ 不要同步状态
$effect(() => { left = total - spent; }); // ❌

Props

// ✅ 依赖 props 的值用 $derived
let { type } = $props();
let color = $derived(type === 'danger' ? 'red' : 'green');

Events

<!-- ✅ 现代事件属性 -->
<button onclick={handleClick}>click</button>

<!-- ❌ 旧式 on:click -->
<button on:click={handleClick}>click</button>

<!-- ✅ 监听 window 事件 -->
<svelte:window onkeydown={handleKey} />

Snippets

<!-- ✅ 推荐:使用 snippet -->
{#snippet footer()}
  <p>footer</p>
{/snippet}

Each blocks

<!-- ✅ 始终使用 key -->
{#each items as item (item.id)}
  <li>{item.name}</li>
{/each}

Context

// ✅ 用 createContext 提供类型安全
import { createContext } from 'svelte';
const [getCtx, setCtx] = createContext<MyCtx>();

Avoid Legacy Features

// ✅ Runes mode 全是新项目的默认
let count = $state(0);     // ✅
$: count++;                  // ❌ legacy
export let name;            // ❌
let { name } = $props();   // ✅
on:click={fn}              // ❌
onclick={fn}               // ✅
<slot />                   // ❌
{@render children()}        // ✅
<svelte:component this={x} /> // ❌
<svelte:element this={x} />   // ✅

Critical: Testing (Vitest)

Installation and configuration

npm install -D vitest
// vite.config.js
export default defineConfig({
  resolve: process.env.VITEST
    ? { conditions: ['browser'] }
    : undefined
});

Unit testing

// multiplier.svelte.js
export function multiplier(initial, k) {
  let count = $state(initial);
  return {
    get value() { return count * k; },
    set: (c) => { count = c; }
  };
}

// test
import { multiplier } from './multiplier.svelte.js';
test('multiplier', () => {
  let double = multiplier(0, 2);
  expect(double.value).toEqual(0);
  double.set(5);
  expect(double.value).toEqual(10);
});

Component testing

import { mount, unmount } from 'svelte';
import { expect, test } from 'vitest';

test('MyComponent', () => {
  const comp = mount(MyComponent, { target: document.body, props: { name: 'Ada' } });
  expect(document.body.innerHTML).toContain('Ada');
  unmount(comp);
});

flushSync

Force all pending effects to run synchronously (commonly used in tests):

import { flushSync } from 'svelte';
flushSync();

Quick Fixes

Problem Solution
State is not synchronized between components Use Context (across levels) or .svelte.js(global)
onMount cleanup does not run Make sure to pass a synchronous function (not async); when async is needed, wrap it in an IIFE
Components are not interactive after SSR Use hydrate instead of mount
State does not update in tests Use flushSync() to force synchronous execution
Context link is broken Do not reassign the entire Context object
effect does not run after mount mount() returns synchronously, but the effect only runs in the next microtask; if you need it to run immediately, flushSync()
CSP blocks hydratable scripts Use csp: { nonce }(dynamic) or csp: { hash: true }(static), and declare it synchronously in the response headers
async onMount cannot get cleanup outer arrow synchronous return () => controller.abort(), put the async logic inside the IIFE

Gotchas

  1. Store $ prefix is subscription syntax — $store is a subscription in both templates and scripts, not $ variables without the prefix are plain values
  2. Context link is broken — reassigning the entire object breaks the reactive link; use .count = 0 instead of obj = {count:0}
  3. mount does not run $effect — effects are triggered by a microtask after the component mounts
  4. In tests $effect.root — tests involving effects need $effect.root() wrapping
  5. render is only available in the SSR environment — on the client use mount
  6. async onMount cleanup is ignored — an async function always returns a Promise, so Svelte cannot get the cleanup function; use the IIFE pattern
  7. hydrate() does not run effects synchronously — like mount() , after hydration you need flushSync() to make effects run
  8. CSP nonce is one-time — a new nonce per request; hash mode conflicts with future streaming SSR
  9. hydration random values will not match — Math.random() / Date.now() differs between SSR and CSR; use hydratable() wrapping

FAQ

Q: When should you use Store vs Context vs .svelte.js? A: Store: simple values shared across the whole app; Context: shared locally within a component tree;.svelte.js $state: the recommended new approach, can replace Store.

Q: tick and await Promise.resolve() what is the difference? A: tick() guarantees resolution after the DOM updates,Promise.resolve() Does not wait for Svelte's update cycle.

Q: tick() and flushSync() how to choose? A: await tick() is suitable for asynchronous user code;flushSync() is suitable for testing and external library synchronization scenarios that need immediate DOM synchronization.

Q: How to test code with $effect ? A: Use flushSync() to force execution, use $effect.root() to wrap the test code.

Q: Hydration error after SSR? A: Ensure the HTML structure of SSR and CSR is consistent; use hydrate instead of mount。

Q: When to use onMount vs $effect? A: onMount is a side effect that "runs once after mounting";$effect is a reactive side effect that "reruns when dependencies change".

Q: hydrate 's inline script is blocked by CSP, what should I do? A: Use csp: { nonce } to add a nonce to the script at render time, and declare it synchronously in the response headers; for static sites, use csp: { hash: true } to get hashes.script。

Examples

Practical examples demonstrating Svelte 5's runtime APIs.

File Description
examples/README.md Entry point with quick reference
examples/mount-hydrate.md 9 examples: mount, unmount, hydrate, render, flushSync, tick
examples/ssr-patterns.md SSR patterns: async render, streaming, hydration, CSP
examples/onmount-tick.md 15 lifecycle examples: onMount (sync/async/cleanup), onDestroy, tick for focus/measure/scroll, $effect.pre pattern
examples/csp-examples.md 8 CSP examples: nonce (Express/SvelteKit/Cloudflare), hash (SSG), diagnostic tips

References

In-depth technical references for Svelte 5's runtime APIs.

File Description
references/README.md Entry point with quick reference
references/mount-unmount-reference.md Deep dive: mount/unmount/hydrate lifecycle, flushSync, tick
references/ssr-reference.md SSR deep dive: render, async SSR, CSP, hydration
references/lifecycle-runtime-reference.md Lifecycle in mount/hydrate runtime: when effects run, onMount timing, tick vs flushSync, SSR phase matrix
references/csp-reference.md CSP nonce vs hash, runtime configuration, streaming compatibility