Claude Code for SvelteKit: Runes, Server Actions, and Full-Stack Patterns — Claude Skills 360 Blog
Blog / Development / Claude Code for SvelteKit: Runes, Server Actions, and Full-Stack Patterns
Development

Claude Code for SvelteKit: Runes, Server Actions, and Full-Stack Patterns

Published: June 10, 2026
Read time: 8 min read
By: Claude Skills 360

SvelteKit’s combination of file-based routing, co-located server code, and Svelte’s minimal runtime makes it one of the most productive full-stack frameworks available. Svelte 5 Runes replace the old reactive declarations with a simpler, explicit API. Claude Code generates SvelteKit code in the modern Runes style — no legacy $: reactive statements.

This guide covers SvelteKit with Claude Code: Svelte 5 Runes, form actions, load functions, server-side rendering, and deployment.

Project Setup

Set up a SvelteKit project with TypeScript, Svelte 5 Runes,
and Drizzle ORM for the database. I want server-side rendering.
## SvelteKit Project Conventions
- SvelteKit 2.x with Svelte 5 Runes (no legacy reactive statements)
- TypeScript strict mode
- File-based routing in src/routes/
- Server code: +page.server.ts (load + actions), +server.ts (API routes)
- Global state: Svelte 5 $state in src/lib/stores/ with context API for SSR safety
- Database: Drizzle ORM with PostgreSQL
- Form validation: superforms with zod schemas
- Styling: Tailwind CSS v4
- Testing: Vitest + @testing-library/svelte
- Deploy target: Cloudflare Pages (@sveltejs/adapter-cloudflare)

Svelte 5 Runes

Create a ProductCard component with add-to-cart functionality.
Use Svelte 5 Runes — no $: reactive statements.
<!-- src/lib/components/ProductCard.svelte -->
<script lang="ts">
  import { cartStore } from '$lib/stores/cart.svelte';

  interface Props {
    product: {
      id: string;
      name: string;
      price: number;
      imageUrl: string;
      inStock: boolean;
    };
    class?: string;
  }

  let { product, class: className = '' }: Props = $props();

  // Local reactive state with $state
  let quantity = $state(1);
  let isAdding = $state(false);
  let justAdded = $state(false);

  // Derived state with $derived
  let totalPrice = $derived(product.price * quantity);
  let canAdd = $derived(product.inStock && quantity > 0);

  async function addToCart() {
    if (!canAdd || isAdding) return;

    isAdding = true;
    try {
      await cartStore.addItem({ productId: product.id, quantity });
      justAdded = true;
      setTimeout(() => (justAdded = false), 2000);
    } finally {
      isAdding = false;
    }
  }
</script>

<article class="product-card {className}">
  <img src={product.imageUrl} alt={product.name} loading="lazy" />

  <div class="details">
    <h3>{product.name}</h3>
    <p class="price">${(product.price / 100).toFixed(2)}</p>

    {#if product.inStock}
      <div class="quantity-control">
        <button
          onclick={() => (quantity = Math.max(1, quantity - 1))}
          aria-label="Decrease quantity"
          disabled={quantity <= 1}
        >−</button>
        <span aria-label="Quantity">{quantity}</span>
        <button
          onclick={() => (quantity += 1)}
          aria-label="Increase quantity"
        >+</button>
      </div>

      <p class="total">Total: ${(totalPrice / 100).toFixed(2)}</p>

      <button
        onclick={addToCart}
        disabled={!canAdd || isAdding}
        class:success={justAdded}
      >
        {#if isAdding}
          Adding...
        {:else if justAdded}
          Added ✓
        {:else}
          Add to cart
        {/if}
      </button>
    {:else}
      <p class="out-of-stock">Out of stock</p>
    {/if}
  </div>
</article>

Key Svelte 5 differences from Svelte 4: $props() replaces export let, $state() replaces regular variables for reactivity, $derived() replaces $: computed, onclick replaces on:click.

SvelteKit Load Functions

The product page needs server-side rendering.
Load the product, check if it's in the user's wishlist,
and handle 404 if not found.
// src/routes/products/[slug]/+page.server.ts
import { error } from '@sveltejs/kit';
import { db } from '$lib/server/db';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ params, locals }) => {
  const product = await db.query.products.findFirst({
    where: (products, { eq }) => eq(products.slug, params.slug),
    with: {
      category: true,
      images: true,
    },
  });

  if (!product) {
    error(404, { message: `Product "${params.slug}" not found` });
  }

  // Load wishlist status in parallel (don't block if not authenticated)
  const wishlistPromise = locals.user
    ? db.query.wishlistItems.findFirst({
        where: (items, { and, eq }) => and(
          eq(items.userId, locals.user!.id),
          eq(items.productId, product.id),
        ),
      })
    : Promise.resolve(null);

  const [inWishlist] = await Promise.all([wishlistPromise]);

  return {
    product,
    inWishlist: !!inWishlist,
    // User data from locals (set by hooks.server.ts)
    user: locals.user ?? null,
  };
};
<!-- src/routes/products/[slug]/+page.svelte -->
<script lang="ts">
  import type { PageData } from './$types';
  
  let { data }: { data: PageData } = $props();
  
  // Data from load function is typed
  let { product, inWishlist, user } = $derived(data);
</script>

<svelte:head>
  <title>{product.name} | My Store</title>
  <meta name="description" content={product.description} />
</svelte:head>

<h1>{product.name}</h1>
<p class="price">${(product.price / 100).toFixed(2)}</p>

Form Actions for Mutations

The wishlist add/remove toggle should work without JavaScript (progressive enhancement).
Use SvelteKit form actions.
// src/routes/products/[slug]/+page.server.ts
import { fail, redirect } from '@sveltejs/kit';
import type { Actions } from './$types';

export const actions: Actions = {
  toggleWishlist: async ({ locals, params, request }) => {
    if (!locals.user) {
      // Redirect to login with return URL
      redirect(303, `/login?redirect=/products/${params.slug}`);
    }

    const product = await db.query.products.findFirst({
      where: (p, { eq }) => eq(p.slug, params.slug),
    });

    if (!product) return fail(404, { message: 'Product not found' });

    const existing = await db.query.wishlistItems.findFirst({
      where: (items, { and, eq }) => and(
        eq(items.userId, locals.user!.id),
        eq(items.productId, product.id),
      ),
    });

    if (existing) {
      await db.delete(wishlistItems).where(eq(wishlistItems.id, existing.id));
      return { inWishlist: false };
    } else {
      await db.insert(wishlistItems).values({
        userId: locals.user.id,
        productId: product.id,
      });
      return { inWishlist: true };
    }
  },
};
<!-- In the page component — works without JS via native form submission -->
<form method="POST" action="?/toggleWishlist">
  <button type="submit">
    {inWishlist ? '♥ Remove from wishlist' : '♡ Add to wishlist'}
  </button>
</form>

<!-- With enhance — progressive enhancement adds JS interactivity -->
<script>
  import { enhance } from '$app/forms';
</script>

<form method="POST" action="?/toggleWishlist" use:enhance>
  <button type="submit">{inWishlist ? '♥ Remove' : '♡ Add'}</button>
</form>

use:enhance intercepts the form submit, makes it a fetch request, and updates the page data — no full page reload. Falls back to standard form submission if JS fails.

Deploying to Cloudflare Pages

Deploy this SvelteKit app to Cloudflare Pages.
I need: edge functions, KV for caching, D1 for the database.
npm i -D @sveltejs/adapter-cloudflare
// svelte.config.js
import adapter from '@sveltejs/adapter-cloudflare';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

export default {
  preprocess: vitePreprocess(),
  kit: {
    adapter: adapter({
      routes: {
        include: ['/*'],
        exclude: ['<all>'],
      },
    }),
  },
};
// src/app.d.ts — types for Cloudflare bindings
declare global {
  namespace App {
    interface Platform {
      env: {
        DB: D1Database;           // Cloudflare D1 SQL
        KV: KVNamespace;          // Cloudflare KV store
        ASSETS: Fetcher;
      };
      context: ExecutionContext;
    }
  }
}
// Accessing Cloudflare bindings in load functions
export const load: PageServerLoad = async ({ platform }) => {
  const db = drizzle(platform!.env.DB); // D1 via Drizzle
  const cached = await platform!.env.KV.get('some-key'); // KV cache
};

For the Cloudflare Workers and Pages deployment architecture beyond SvelteKit, see the serverless guide. For comparing SvelteKit with Next.js and Vue/Nuxt for your specific project, see the Claude Skills 360 bundle which includes framework decision-making skill sets. For authentication patterns in SvelteKit, see the authentication guide. Start with the free tier to try SvelteKit project generation.

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free