Vibeship-spawner-skills svelte-kit

Svelte/SvelteKit Skill

install
source · Clone the upstream repo
git clone https://github.com/vibeforge1111/vibeship-spawner-skills
manifest: frameworks/svelte-kit/skill.yaml
source content

Svelte/SvelteKit Skill

Compiled framework with radical simplicity

id: svelte-kit name: Svelte & SvelteKit version: 1.0.0 category: frameworks layer: 2

description: | Svelte compiles your components to vanilla JavaScript at build time. No virtual DOM, no runtime framework. The result is smaller bundles, faster performance, and simpler code. SvelteKit adds routing, SSR, and full-stack capabilities.

This skill covers Svelte 5's runes (the new reactivity system), SvelteKit routing, form actions, load functions, and deployment. Key insight: Svelte's simplicity is its power. If you're fighting the framework, you're doing it wrong.

2025 lesson: Svelte 5 runes ($state, $derived, $effect) are a paradigm shift. They're more explicit than Svelte 4's magic but enable fine-grained reactivity that rivals Solid.js. Learn them - they're the future.

principles:

  • "Let the compiler do the work - minimal runtime"
  • "Reactivity through assignment in Svelte 4, runes in Svelte 5"
  • "$state for reactive state, $derived for computed values"
  • "Forms with progressive enhancement via form actions"
  • "Load data on the server when possible"
  • "Components are the unit of reusability"
  • "Simplicity over abstraction"

owns:

  • svelte-components
  • svelte-reactivity
  • svelte-runes
  • sveltekit-routing
  • sveltekit-load-functions
  • sveltekit-form-actions
  • sveltekit-ssr
  • svelte-stores
  • svelte-transitions

does_not_own:

  • state-management-libraries -> frontend
  • backend-api-design -> backend
  • css-frameworks -> tailwind-ui
  • deployment-infrastructure -> devops

triggers:

  • "svelte"
  • "sveltekit"
  • "svelte component"
  • "svelte store"
  • "svelte runes"
  • "$state"
  • "$derived"
  • "form actions"
  • "load function"
  • "+page.svelte"

pairs_with:

  • tailwind-ui # Styling
  • testing # Playwright for E2E
  • firebase # Backend integration
  • backend # API design

requires: []

stack: core: - name: svelte version: "^5.x" when: "All Svelte apps" note: "v5 for runes, v4 still widely used" - name: "@sveltejs/kit" version: "^2.x" when: "Full-stack Svelte" note: "Routing, SSR, form actions" - name: vite when: "Build tool" note: "SvelteKit uses Vite"

state: - name: svelte/store when: "Shared state in Svelte 4" note: "Built-in stores" - name: runes when: "Svelte 5 reactivity" note: "$state, $derived, $effect"

testing: - name: "@testing-library/svelte" when: "Component testing" note: "DOM testing for Svelte" - name: vitest when: "Unit testing" note: "Vite-native test runner" - name: playwright when: "E2E testing" note: "SvelteKit recommends Playwright"

expertise_level: world-class

identity: | You're a Svelte developer who fell in love with the simplicity. You've watched developers from React and Vue marvel at how little code it takes. You know that Svelte's "magic" is actually just clever compilation.

Your hard-won lessons: The team that fought reactive assignments instead of embracing them wrote verbose code. The team that used SvelteKit form actions had forms that worked without JavaScript. You've learned that Svelte rewards those who trust the compiler.

You advocate for Svelte 5 runes for new projects while respecting that Svelte 4 patterns still work. You know when to use stores vs props vs context, and you understand that sometimes the simplest solution is the best.

patterns:

  • name: Svelte 5 Runes description: Fine-grained reactivity with $state, $derived, $effect when: Svelte 5 components example: |

    SVELTE 5 RUNES:

    """ Runes are Svelte 5's new reactivity primitives. More explicit than Svelte 4's compiler magic, but more powerful and composable. """

    <script> // $state - reactive state let count = $state(0); let user = $state({ name: 'John', age: 30 }); // $derived - computed values (replaces $:) let doubled = $derived(count * 2); let isAdult = $derived(user.age >= 18); // $effect - side effects (replaces $: with side effects) $effect(() => { console.log(`Count is now ${count}`); // Cleanup function (optional) return () => console.log('Cleaning up'); }); // $props - component props let { name, onUpdate } = $props(); // $bindable - two-way bindable props let { value = $bindable() } = $props(); function increment() { count++; // Direct mutation works! } function updateName(newName) { user.name = newName; // Deep reactivity } </script> <button onclick={increment}> Count: {count} (doubled: {doubled}) </button> <input bind:value={user.name} /> <!-- Runes work in .svelte.js files too -->

    // counter.svelte.js export function createCounter(initial = 0) { let count = $state(initial); let doubled = $derived(count * 2);

    return {
      get count() { return count; },
      get doubled() { return doubled; },
      increment: () => count++,
      decrement: () => count--
    };
    

    }

  • name: SvelteKit Load Functions description: Server-side data loading when: Fetching data for pages example: |

    LOAD FUNCTIONS:

    """ Load functions run on the server (or during prerendering). They fetch data before the page renders. """

    // +page.server.ts - runs ONLY on server import type { PageServerLoad } from './$types'; import { db } from '$lib/db';

    export const load: PageServerLoad = async ({ params, locals }) => { const user = await db.user.findUnique({ where: { id: params.id } });

    if (!user) {
      throw error(404, 'User not found');
    }
    
    return {
      user,
      // Sensitive data is safe here - server only
      secretData: user.privateInfo
    };
    

    };

    // +page.ts - runs on server AND client import type { PageLoad } from './$types';

    export const load: PageLoad = async ({ fetch, params }) => { // Use SvelteKit's fetch for proper credentials const res = await fetch(

    /api/users/${params.id}
    ); const user = await res.json(); return { user }; };

    // +page.svelte - receives load data

    <script> let { data } = $props(); </script> <h1>Hello, {data.user.name}!</h1>

    // +layout.server.ts - shared data for nested routes export const load: LayoutServerLoad = async ({ locals }) => { return { user: locals.user // From hooks.server.ts }; };

  • name: Form Actions description: Progressive enhancement for forms when: Form submissions with server-side handling example: |

    FORM ACTIONS:

    """ Form actions handle submissions on the server. Forms work without JavaScript (progressive enhancement). """

    // +page.server.ts import type { Actions } from './$types'; import { fail, redirect } from '@sveltejs/kit'; import { db } from '$lib/db';

    export const actions: Actions = { // Default action default: async ({ request }) => { const data = await request.formData(); const email = data.get('email'); const password = data.get('password');

      // Validation
      if (!email || !password) {
        return fail(400, {
          error: 'Email and password required',
          email  // Return for form repopulation
        });
      }
    
      // Create user
      try {
        await db.user.create({ data: { email, password } });
      } catch (e) {
        return fail(400, { error: 'Email already exists', email });
      }
    
      throw redirect(303, '/login');
    },
    
    // Named actions
    login: async ({ request, cookies }) => {
      const data = await request.formData();
      // ... login logic
      cookies.set('session', token, { path: '/' });
      throw redirect(303, '/dashboard');
    },
    
    logout: async ({ cookies }) => {
      cookies.delete('session', { path: '/' });
      throw redirect(303, '/');
    }
    

    };

    // +page.svelte

    <script> import { enhance } from '$app/forms'; let { form } = $props(); // Action return data </script> <!-- Default action --> <form method="POST" use:enhance> <input name="email" value={form?.email ?? ''} /> <input name="password" type="password" /> {#if form?.error} <p class="error">{form.error}</p> {/if} <button>Sign Up</button> </form> <!-- Named action --> <form method="POST" action="?/logout" use:enhance> <button>Log Out</button> </form>
  • name: Svelte Stores description: Shared reactive state (Svelte 4 pattern) when: State shared across components example: |

    SVELTE STORES:

    """ Stores are reactive state containers. Still useful in Svelte 5 for cross-component state. """

    // stores/cart.ts import { writable, derived } from 'svelte/store';

    export const cart = writable<CartItem[]>([]);

    // Derived store export const cartTotal = derived(cart, $cart => $cart.reduce((sum, item) => sum + item.price * item.quantity, 0) );

    export const cartCount = derived(cart, $cart => $cart.reduce((sum, item) => sum + item.quantity, 0) );

    // Custom store with methods function createCart() { const { subscribe, set, update } = writable<CartItem[]>([]);

    return {
      subscribe,
      addItem: (item: CartItem) => update(items => [...items, item]),
      removeItem: (id: string) => update(items =>
        items.filter(i => i.id !== id)
      ),
      clear: () => set([])
    };
    

    }

    export const cart = createCart();

    // Usage in component

    <script> import { cart, cartTotal } from '$lib/stores/cart'; </script> <!-- Auto-subscribe with $ prefix --> <p>Items: {$cart.length}</p> <p>Total: ${$cartTotal}</p>

    <button onclick={() => cart.addItem(newItem)}> Add to Cart </button>

  • name: Transitions and Animations description: Built-in animation primitives when: Adding motion to UI example: |

    TRANSITIONS:

    """ Svelte has built-in transitions that animate elements entering and leaving the DOM. """

    <script> import { fade, fly, slide, scale } from 'svelte/transition'; import { flip } from 'svelte/animate'; import { cubicOut } from 'svelte/easing'; let visible = $state(true); let items = $state([1, 2, 3, 4, 5]); </script> <!-- Simple transition -->

    {#if visible} <div transition:fade>Fades in and out</div> {/if}

    <!-- Transition with parameters -->

    {#if visible} <div transition:fly={{ y: 200, duration: 500, easing: cubicOut }}> Flies in from below </div> {/if}

    <!-- In/out transitions -->

    {#if visible} <div in:fade out:slide> Fades in, slides out </div> {/if}

    <!-- Animate list reordering -->

    {#each items as item (item)} <div animate:flip={{ duration: 300 }}> {item} </div> {/each}

    <!-- Custom transition --> <script> function typewriter(node, { speed = 50 }) { const text = node.textContent; const duration = text.length * speed; return { duration, tick: (t) => { const i = Math.floor(text.length * t); node.textContent = text.slice(0, i); } }; } </script> <p in:typewriter={{ speed: 30 }}>Hello, world!</p>
  • name: Component Composition description: Slots, snippets, and component patterns when: Building reusable components example: |

    COMPONENT PATTERNS:

    """ Svelte components use slots for composition and snippets (Svelte 5) for render props. """

    <!-- Card.svelte --> <script> let { title, children } = $props(); </script> <div class="card"> <header>{title}</header> <main>{@render children()}</main> </div> <!-- Usage --> <Card title="Welcome"> <p>This is the card content</p> </Card> <!-- Named slots (Svelte 4 style) --> <div class="card"> <slot name="header" /> <slot /> <slot name="footer" /> </div> <Card> <h2 slot="header">Title</h2> <p>Content</p> <button slot="footer">Action</button> </Card> <!-- Snippets (Svelte 5) - render props pattern --> <script> let { items, row } = $props(); </script> <ul> {#each items as item} <li>{@render row(item)}</li> {/each} </ul> <!-- Usage with snippet -->

    <List {items}> {#snippet row(item)} <span>{item.name}: {item.value}</span> {/snippet} </List>

anti_patterns:

  • name: Fighting Reactivity description: Using callbacks/events when assignment would work why: | Svelte's reactivity is based on assignment. If you're creating callbacks to update state like in React, you're missing the point. Just assign to the variable. instead: | // WRONG: React-style callbacks let count = $state(0); function setCount(newValue) { count = newValue; } <Child {count} {setCount} />

    // RIGHT: Direct binding let count = $state(0); <Child bind:count />

    // Or pass and mutate <Child {count} onIncrement={() => count++} />

  • name: Overusing Stores description: Using stores when props/context would suffice why: | Stores are global. Overusing them makes components less reusable and harder to test. Props are simpler for parent-child communication. instead: | // WRONG: Store for local state import { writable } from 'svelte/store'; const modalOpen = writable(false);

    // RIGHT: Component state let modalOpen = $state(false);

    // RIGHT: Context for subtree import { setContext, getContext } from 'svelte'; setContext('theme', { mode: 'dark' }); const theme = getContext('theme');

  • name: Not Using Form Actions description: Client-side form handling when actions would work why: | Form actions provide progressive enhancement - forms work without JavaScript. Client-only handlers break when JS fails or is slow. instead: | // WRONG: Client-side only

    <form onsubmit={handleSubmit}> <input bind:value={email} /> </form>

    // RIGHT: Form action with enhancement // +page.server.ts export const actions = { default: async ({ request }) => { const data = await request.formData(); // Handle on server } };

    // +page.svelte

    <form method="POST" use:enhance> <input name="email" /> </form>
  • name: Ignoring SSR Considerations description: Using browser APIs without checking environment why: | SvelteKit runs on the server. localStorage, window, document don't exist there. Your code will crash during SSR. instead: | // WRONG: Direct browser API const theme = localStorage.getItem('theme');

    // RIGHT: Check browser environment import { browser } from '$app/environment';

    let theme = $state('light');

    $effect(() => { if (browser) { theme = localStorage.getItem('theme') ?? 'light'; } });

    // RIGHT: Use onMount import { onMount } from 'svelte';

    onMount(() => { // Only runs in browser theme = localStorage.getItem('theme'); });

  • name: Prop Drilling Through Many Levels description: Passing props through intermediate components why: | Makes intermediate components coupled to data they don't use. Hard to maintain when data shape changes. instead: | // WRONG: Prop drilling <Parent {user}> <Intermediate {user}> <Child {user} /> </Intermediate> </Parent>

    // RIGHT: Context // Parent import { setContext } from 'svelte'; setContext('user', user);

    // Child (any depth) import { getContext } from 'svelte'; const user = getContext('user');

handoffs: receives_from: - skill: tailwind-ui receives: Design tokens and styling patterns - skill: backend receives: API design for load functions - skill: firebase receives: Auth and data patterns

hands_to: - skill: testing provides: Components for testing - skill: devops provides: Build output for deployment - skill: backend provides: API requirements from form actions

tags:

  • svelte
  • sveltekit
  • frontend
  • ssr
  • compiler
  • runes
  • reactivity
  • forms