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 커뮤니티가 만든 AI 스킬을 발견하고 탐색하세요.
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