Skip to main content 首页 创作者 bobmatnyc claude-mpm-skills svelte5-runes-static
svelte5-runes-static Svelte 5 runes + SvelteKit adapter-static (SSG/SSR) patterns for hydration-safe state, store bridges, and reactivity that survives prerendering
跳到安装 Skills Marketplace 发现并探索由社区构建的 Agent Skills
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill svelte5-runes-static命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
下载 Zip 下载中... 同仓库更多 Skills LinkedIn automation via the Linked API CLI - fetch profiles, search people and companies, send messages, manage connections, create posts, react, comment, and run Sales Navigator and custom workflows. Use when the user wants to interact with LinkedIn.
Xquik X data automation API - Use REST or MCP for tweet search, user lookup, follower exports, media downloads, monitors, webhooks, giveaway draws, and confirmation-gated X actions.
MCP (Model Context Protocol) - Build AI-native servers with tools, resources, and prompts. TypeScript/Python SDKs for Claude Desktop integration.
name svelte5-runes-static description Svelte 5 runes + SvelteKit adapter-static (SSG/SSR) patterns for hydration-safe state, store bridges, and reactivity that survives prerendering user-invocable false disable-model-invocation true version 1.0.0 category toolchain author Claude MPM Team license MIT progressive_disclosure {"entry_point":{"summary":"Hydration-safe Svelte 5 runes patterns for SvelteKit adapter-static (SSG/SSR)","when_to_use":"When using Svelte 5 runes with SvelteKit prerendering/adapter-static and encountering frozen state, hydration mismatches, or store bridge issues","quick_start":"1. Keep global state in writable()/derived() 2. Bridge store → $state in $effect() 3. Use $derived() for computations 4. Guard client-only code with browser"}} context_limit 150 tags ["svelte","svelte5","sveltekit","runes","adapter-static","ssr","ssg","hydration","stores","reactivity","prerendering","typescript"] requires_tools []
Svelte 5 Runes with adapter-static (SvelteKit)
Overview
Build static-first SvelteKit applications with Svelte 5 runes without breaking hydration. Apply these patterns when using adapter-static (prerendering) and combining global stores with component-local runes.
Related Skills
svelte (Svelte 5 runes core patterns)
sveltekit (adapters, deployment, SSR/SSG patterns)
typescript-core (TypeScript patterns and validation)
vitest (unit testing patterns)
Core Expertise
Building static-first Svelte 5 applications using runes mode with proper state management patterns that survive prerendering and hydration.
Critical Compatibility Rules
❌ NEVER: Runes in Module Scope with adapter-static
Problem : Runes don't hydrate properly after static prerendering
export function createStore ( ) {
let state = $state({ count : 0 });
return {
get count () { return state.count ; },
increment : () => { state.count ++; }
};
}
Why it fails :
adapter-static prerenders components to HTML
Runes in module scope don't serialize/deserialize
State becomes inert/frozen after hydration
Reactivity completely breaks
Solution : Use traditional writable() stores for global state
import { writable } from 'svelte/store' ;
export function ( ) {
count = ( );
{
count,
: count. ( n + )
};
}
createStore
const
writable
0
return
increment
() =>
update
n =>
1
❌ NEVER: $ Auto-subscription Inside $derived Problem : Runes mode disables $ auto-subscription syntax
let filtered = $derived($events.filter (e => e.type === 'info' ));
Solution : Subscribe in $effect() → update $state() → use in $derived()
import { type Writable } from 'svelte/store' ;
let events = $state<Event []>([]);
$effect(() => {
const unsub = eventsStore.subscribe (value => {
events = value;
});
return unsub;
});
let filtered = $derived(events.filter (e => e.type === 'info' ));
❌ NEVER: Store Factory with Getters Problem : Getters don't establish reactive connections
export function createSocketStore ( ) {
const socket = writable<Socket | null >(null );
return {
get socket () { return socket; },
connect : () => { }
};
}
Solution : Export stores directly
export function createSocketStore ( ) {
const socket = writable<Socket | null >(null );
const isConnected = derived (socket, $s => $s?.connected ?? false );
return {
socket,
isConnected,
connect : () => { }
};
}
Recommended Hybrid Pattern
Global State: Traditional Stores Use writable()/derived() for state that needs to survive SSG/SSR:
import { writable, derived } from 'svelte/store' ;
export const user = writable<User | null >(null );
export const theme = writable<'light' | 'dark' >('light' );
export const isAuthenticated = derived (user, $u => $u !== null );
Component State: Svelte 5 Runes Use runes for component-local state and logic:
<script lang="ts" >
import { user } from '$lib/stores/globalState' ;
let {
initialCount = 0 ,
onUpdate = () => {}
}: {
initialCount ?: number ;
onUpdate ?: (count : number ) => void ;
} = $props();
let currentUser = $state<User | null >(null );
$effect(() => {
const unsub = user.subscribe (u => {
currentUser = u;
});
return unsub;
});
let count = $state(initialCount);
let doubled = $derived(count * 2 );
$effect(() => {
if (count > 10 ) {
onUpdate (count);
}
});
function increment ( ) {
count++;
}
</script>
<button onclick ={increment} >
{currentUser?.name ?? 'Guest'}: {count} (×2 = {doubled})
</button >
Complete Bridge Pattern
Store → Rune → Derived Chain <script lang="ts" >
import { type Writable } from 'svelte/store' ;
const { events : eventsStore, filters : filtersStore } = myGlobalStore;
let events = $state<Event []>([]);
let activeFilters = $state<string []>([]);
$effect(() => {
const unsubEvents = eventsStore.subscribe (v => { events = v; });
const unsubFilters = filtersStore.subscribe (v => { activeFilters = v; });
return () => {
unsubEvents ();
unsubFilters ();
};
});
let filtered = $derived(
events.filter (e =>
activeFilters.length === 0 ||
activeFilters.includes (e.category )
)
);
let count = $derived(filtered.length );
let hasEvents = $derived(count > 0 );
</script>
{#if hasEvents}
<p>Found {count} events</p>
{#each filtered as event}
<EventCard {event} />
{/each}
{:else }
<p>No events match filters</p>
{/if }
SSG/SSR Considerations
Prerender-Safe Patterns
export const load = async ({ fetch } ) => {
const data = await fetch ('/api/data' ).then (r => r.json ());
return { data };
};
<script lang="ts">
import { browser } from '$app/environment';
let { data } = $props();
// ✅ Client-only initialization
$effect(() => {
if (browser) {
// WebSocket, localStorage, etc.
initializeClientOnlyFeatures();
}
});
</script>
Hydration Mismatch Prevention
let timestamp = $state<number | null >(null );
$effect(() => {
if (browser) {
timestamp = Date .now ();
}
});
<!-- ✅ Conditional rendering for client-only content -->
{#if browser}
<LiveClock />
{:else}
<p>Loading clock...</p>
{/if}
TypeScript Integration
Typed Props with Runes <script lang="ts" >
import type { Snippet } from 'svelte' ;
interface Props {
title : string ;
count ?: number ;
items : Array <{ id : string ; name : string }>;
onSelect ?: (id : string ) => void ;
children ?: Snippet ;
}
let {
title,
count = 0 ,
items,
onSelect = () => {},
children
}: Props = $props();
let selected = $state<string | null >(null );
let filteredItems = $derived(
items.filter (item =>
selected === null || item.id === selected
)
);
</script>
<h2 > {title} ({count})</h2 >
{#each filteredItems as item}
<button onclick={() => onSelect (item.id )}>
{item.name }
</button>
{/each}
{@render children?.()}
Typed Store Bridges <script lang="ts" >
import type { Writable , Readable } from 'svelte/store' ;
interface StoreShape {
data : Writable <string []>;
status : Readable <'loading' | 'ready' | 'error' >;
}
const stores : StoreShape = getMyStores ();
let data = $state<string []>([]);
let status = $state<'loading' | 'ready' | 'error' >('loading' );
$effect(() => {
const unsubData = stores.data .subscribe (v => { data = v; });
const unsubStatus = stores.status .subscribe (v => { status = v; });
return () => {
unsubData ();
unsubStatus ();
};
});
let isEmpty = $derived(data.length === 0 );
let isReady = $derived(status === 'ready' );
</script>
Common Patterns
Bindable Component State <script lang="ts" >
let {
value = $bindable('' ),
disabled = false
}: {
value ?: string ;
disabled ?: boolean ;
} = $props();
let focused = $state(false );
let charCount = $derived(value.length );
let isValid = $derived(charCount >= 3 && charCount <= 100 );
</script>
<input
bind:value
{disabled }
onfocus ={() => { focused = true; }}
onblur={() => { focused = false; }}
class:focused
class:invalid={!isValid}
/>
<p > {charCount}/100</p >
Form State Management <script lang="ts" >
interface FormData {
email : string ;
password : string ;
}
let formData = $state<FormData >({
email : '' ,
password : ''
});
let errors = $state<Partial <Record <keyof FormData , string >>>({});
let isValid = $derived(
formData.email .includes ('@' ) &&
formData.password .length >= 8
);
let canSubmit = $derived(
isValid && Object .keys (errors).length === 0
);
function validate (field : keyof FormData ) {
if (field === 'email' && !formData.email .includes ('@' )) {
errors.email = 'Invalid email' ;
} else if (field === 'password' && formData.password .length < 8 ) {
errors.password = 'Password too short' ;
} else {
delete errors[field];
}
}
async function handleSubmit ( ) {
if (!canSubmit) return ;
const result = await submitForm (formData);
if (result.ok ) {
} else {
errors = result.errors ;
}
}
</script>
<form onsubmit ={handleSubmit} >
<input
type ="email"
bind:value ={formData.email}
onblur ={() => validate('email')}
/>
{#if errors.email}
<span class ="error" > {errors.email}</span >
{/if}
<input
type ="password"
bind:value ={formData.password}
onblur ={() => validate('password')}
/>
{#if errors.password}
<span class ="error" > {errors.password}</span >
{/if}
<button type ="submit" disabled ={!canSubmit} >
Submit
</button >
</form >
Debounced Search <script lang="ts" >
import { writable, derived } from 'svelte/store' ;
const searchQuery = writable ('' );
const debouncedQuery = derived (
searchQuery,
($query, set ) => {
const timeout = setTimeout (() => set ($query), 300 );
return () => clearTimeout (timeout);
},
''
);
let query = $state('' );
let debouncedValue = $state('' );
$effect(() => {
searchQuery.set (query);
});
$effect(() => {
const unsub = debouncedQuery.subscribe (v => {
debouncedValue = v;
});
return unsub;
});
let results = $derived(
debouncedValue.length >= 3
? performSearch (debouncedValue)
: []
);
</script>
<input
type ="search"
bind:value ={query}
placeholder ="Search..."
/>
{#each results as result}
<SearchResult {result} />
{/each}
Migration Checklist When migrating from Svelte 4 to Svelte 5 with adapter-static:
Testing Patterns
Unit Testing Runes import { mount } from 'svelte' ;
import { tick } from 'svelte' ;
import { describe, it, expect } from 'vitest' ;
import Counter from './Counter.svelte' ;
describe ('Counter' , () => {
it ('increments count' , async () => {
const { component } = mount (Counter , {
target : document .body ,
props : { initialCount : 0 }
});
const button = document .querySelector ('button' );
button?.click ();
await tick ();
expect (button?.textContent ).toContain ('1' );
});
});
Testing Store Bridges import { get } from 'svelte/store' ;
import { tick } from 'svelte' ;
import { describe, it, expect } from 'vitest' ;
import { createMyStore } from './myStore' ;
describe ('Store Bridge' , () => {
it ('syncs store to rune state' , async () => {
const store = createMyStore ();
store.data .set (['item1' , 'item2' ]);
await tick ();
expect (get (store.data )).toEqual (['item1' , 'item2' ]);
});
});
Performance Considerations
Avoid Unnecessary Reactivity
let items = $state([1 , 2 , 3 , 4 , 5 ]);
let doubled = $derived(items.map (x => x * 2 ));
let tripled = $derived(items.map (x => x * 3 ));
let quadrupled = $derived(items.map (x => x * 4 ));
let items = $state([1 , 2 , 3 , 4 , 5 ]);
let transformedItems = $derived(
mode === 'double' ? items.map (x => x * 2 ) :
mode === 'triple' ? items.map (x => x * 3 ) :
items.map (x => x * 4 )
);
Memoize Expensive Computations
const expensiveComputation = derived (
[source1, source2],
([$s1, $s2] ) => {
return complexAlgorithm ($s1, $s2);
}
);
let result = $state(null );
$effect(() => {
const unsub = expensiveComputation.subscribe (v => { result = v; });
return unsub;
});
Troubleshooting
Symptom: State doesn't update after hydration Cause : Runes in module scope with adapter-static
Fix : Use traditional writable() stores for global state
Symptom: "$ is not defined" error in $derived Cause : Trying to use $store syntax in runes mode
Fix : Use bridge pattern with $effect() subscription
Symptom: "Cannot read property of undefined" after SSG Cause : Store factory with getters instead of direct exports
Fix : Export stores directly, not wrapped in getters
Symptom: Hydration mismatch warnings Cause : Client-only state rendered during SSR
Fix : Guard with browser check or use {#if browser}
Decision Framework Use Traditional Stores When:
State needs to survive SSG/SSR prerendering
State is global/shared across components
State needs to be serialized/deserialized
Working with adapter-static
State is component-local
Building reactive UI logic
Working with props and component lifecycle
Creating derived computations from local state
Need to combine global stores with component runes
Want derived computations from store values
Building complex reactive chains
Related Skills
toolchains-javascript-frameworks-svelte : Base Svelte patterns
toolchains-typescript-core : TypeScript integration
toolchains-ui-styling-tailwind : Styling Svelte components
toolchains-javascript-testing-vitest : Testing Svelte 5
References