首页 › tutorials › Claude Code Svelte 5全栈:Runes到SvelteKit交付

Claude Code Svelte 5 全栈实战:Runes 响应式到 SvelteKit 项目交付

概述

本教程带你使用 Claude Code 从零搭建一个 Svelte 5 + SvelteKit 全栈任务管理应用。你将掌握 Runes 响应式系统($state、$derived、$effect、$props)、SvelteKit 路由与 Server Actions、以及如何通过 CLAUDE.md 让 AI 深度理解 Svelte 5 项目上下文。适合有前端基础但第一次接触 Svelte 5 的开发者,预计 3-4 小时完成。

适用人群:有 HTML/CSS/JS 基础的前端开发者,了解 React/Vue 但想尝试 Svelte 5 的工程师。

前置条件

条件 说明
Node.js >= 18.x(推荐 20 LTS)
Claude Code 已安装并配置好 API Key
编辑器 VS Code + Svelte 扩展
基础知识 HTML/CSS/JS、基本终端操作

第一步:项目初始化与 CLAUDE.md 配置

1.1 创建 SvelteKit 项目

在终端中运行:

npm create svelte@latest taskflow
cd taskflow
npm install

创建向导中选择: - Skeleton project(骨架项目,干净起步) - Yes, using TypeScript syntax - Yes, add ESLint - Yes, add Prettier - Yes, add Playwright - No, add Vitest(本教程暂不涉及单元测试)

安装完成后,用 Claude Code 打开项目:

claude

1.2 编写 Svelte 5 专用的 CLAUDE.md

这一步至关重要。Svelte 5 的 Runes 语法与 Svelte 4 完全不同,如果 CLAUDE.md 不明确声明版本,Claude Code 可能生成旧语法代码。

在项目根目录创建 CLAUDE.md:

# TaskFlow - Svelte 5 Task Management App

## Tech Stack
- Svelte 5 (Runes mode, NOT Svelte 4 syntax)
- SvelteKit 2.x
- TypeScript
- TailwindCSS 4
- SQLite (via better-sqlite3)

## Critical Rules
- ALWAYS use Svelte 5 Runes syntax ($state, $derived, $effect, $props, $bindable)
- NEVER use Svelte 4 syntax ($:, stores, slot, on:click, createEventDispatcher)
- Use {#snippet} and {@render} instead of <slot>
- Use onclick={} instead of on:click={}
- Use bind:value={} instead of bind:value
- Server code goes in +page.server.ts, +layout.server.ts, +server.ts
- Client state stays in +page.svelte components
- Use form actions for mutations, never direct API calls for simple CRUD

## Build & Dev Commands
- `npm run dev` — Start dev server
- `npm run build` — Production build
- `npm run preview` — Preview production build
- `npm run lint` — Run ESLint
- `npm run format` — Run Prettier

## File Structure
- src/routes/ — SvelteKit file-based routing
- src/lib/components/ — Reusable Svelte components
- src/lib/server/ — Server-only code (db, auth)
- src/lib/stores/ — Shared state utilities
- static/ — Static assets

## Database
- Using better-sqlite3 with src/lib/server/db.ts
- Schema defined in src/lib/server/schema.sql

为什么这么写? 关键在于 Critical Rules 部分。Svelte 5 和 Svelte 4 的语法差异巨大,必须在 CLAUDE.md 中明确禁止旧语法。否则 Claude Code 会混用两代语法,导致编译报错。

1.3 安装额外依赖

在 Claude Code 会话中输入:

请安装以下依赖:tailwindcss @tailwindcss/vite better-sqlite3 及其类型定义

Claude Code 会执行:

npm install tailwindcss @tailwindcss/vite better-sqlite3
npm install -D @types/better-sqlite3

配置 Tailwind CSS。编辑 vite.config.ts:

import { sveltekit } from '@sveltejs/kit/vite';
import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [tailwindcss(), sveltekit()]
});

创建 src/app.css:

@import 'tailwindcss';

1.4 验证开发环境

请运行 npm run dev,确认开发服务器启动正常

预期输出:

  VITE v6.x.x  ready in xxx ms

  ➜  Local:   http://localhost:5173/

常见问题:

问题 原因 解决
Cannot find module '@sveltejs/kit' 未完成 npm install 运行 npm install
Tailwind 类不生效 未导入 app.css 在 +layout.svelte 中 import '../app.css'
Svelte 5 Runes 报错 项目不是 Svelte 5 检查 package.json 中 svelte 版本 >= 5.0

第二步:数据库层搭建

2.1 创建 Schema

创建 src/lib/server/schema.sql:

CREATE TABLE IF NOT EXISTS tasks (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  title TEXT NOT NULL,
  description TEXT DEFAULT '',
  status TEXT DEFAULT 'todo' CHECK(status IN ('todo', 'in-progress', 'done')),
  priority TEXT DEFAULT 'medium' CHECK(priority IN ('low', 'medium', 'high')),
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);

2.2 创建数据库连接模块

创建 src/lib/server/db.ts:

import Database from 'better-sqlite3';
import { readFileSync } from 'fs';
import { join } from 'path';

const db = new Database(join(process.cwd(), 'taskflow.db'));

// 启用 WAL 模式提升并发性能
db.pragma('journal_mode = WAL');

// 初始化表结构
const schema = readFileSync(join(process.cwd(), 'src/lib/server/schema.sql'), 'utf-8');
db.exec(schema);

export interface Task {
  id: number;
  title: string;
  description: string;
  status: 'todo' | 'in-progress' | 'done';
  priority: 'low' | 'medium' | 'high';
  created_at: string;
  updated_at: string;
}

export function getAllTasks(): Task[] {
  return db.prepare('SELECT * FROM tasks ORDER BY created_at DESC').all() as Task[];
}

export function getTaskById(id: number): Task | undefined {
  return db.prepare('SELECT * FROM tasks WHERE id = ?').get(id) as Task | undefined;
}

export function createTask(data: Pick<Task, 'title' | 'description' | 'priority'>): Task {
  const stmt = db.prepare(
    'INSERT INTO tasks (title, description, priority) VALUES (?, ?, ?)'
  );
  const result = stmt.run(data.title, data.description, data.priority);
  return getTaskById(result.lastInsertRowid as number)!;
}

export function updateTask(id: number, data: Partial<Pick<Task, 'title' | 'description' | 'status' | 'priority'>>): Task | undefined {
  const fields = Object.keys(data).map(k => `${k} = @${k}`).join(', ');
  const stmt = db.prepare(`UPDATE tasks SET ${fields}, updated_at = CURRENT_TIMESTAMP WHERE id = @id`);
  stmt.run({ ...data, id });
  return getTaskById(id);
}

export function deleteTask(id: number): boolean {
  const result = db.prepare('DELETE FROM tasks WHERE id = ?').run(id);
  return result.changes > 0;
}

2.3 Claude Code 提示词技巧

告诉 Claude Code:

请创建 src/lib/server/db.ts,使用 better-sqlite3 实现任务 CRUD 操作。
要求:TypeScript 类型安全、WAL 模式、参数化查询防注入。

Claude Code 会根据 CLAUDE.md 中定义的 src/lib/server/ 目录结构,自动将文件放在正确位置。


第三步:Svelte 5 Runes 核心概念实战

这是本教程的核心部分。Svelte 5 引入了 Runes(符文)系统,彻底重构了响应式模型。

3.1 $state — 响应式状态

在 Svelte 4 中,赋值触发响应式:

<!-- Svelte 4(已过时,禁止使用) -->
<script>
  let count = 0;
  function increment() {
    count += 1; // 赋值 = 响应式
  }
</script>

在 Svelte 5 中,使用 $state rune:

<!-- Svelte 5 Runes -->
<script lang="ts">
  let count = $state(0);
  function increment() {
    count++; // 无需赋值技巧,直接修改即触发更新
  }
</script>

<button onclick={increment}>
  Count: {count}
</button>

关键区别:$state 是细粒度响应式,只更新实际变化的部分,不触发整个组件重新渲染。

3.2 $derived — 派生状态

<script lang="ts">
  let tasks = $state<Task[]>([]);
  let filter = $state<'all' | 'todo' | 'in-progress' | 'done'>('all');

  // $derived 自动追踪依赖,当 tasks 或 filter 变化时重新计算
  let filteredTasks = $derived(
    filter === 'all' ? tasks : tasks.filter(t => t.status === filter)
  );

  // 统计数据也是派生的
  let stats = $derived({
    total: tasks.length,
    todo: tasks.filter(t => t.status === 'todo').length,
    inProgress: tasks.filter(t => t.status === 'in-progress').length,
    done: tasks.filter(t => t.status === 'done').length,
  });
</script>

3.3 $effect — 副作用

<script lang="ts">
  let searchQuery = $state('');
  let searchResults = $state<Task[]>([]);

  // $effect 自动追踪内部读取的响应式值
  $effect(() => {
    // 当 searchQuery 变化时自动执行
    if (searchQuery.length >= 2) {
      searchResults = tasks.filter(t =>
        t.title.toLowerCase().includes(searchQuery.toLowerCase())
      );
    } else {
      searchResults = [];
    }
  });

  // 带清理函数的 effect(如防抖、事件监听)
  $effect(() => {
    const timer = setTimeout(() => {
      console.log('搜索:', searchQuery);
    }, 300);

    return () => clearTimeout(timer); // 清理函数
  });
</script>

3.4 $props — 组件属性

<!-- TaskCard.svelte -->
<script lang="ts">
  import type { Task } from '$lib/server/db';

  // Svelte 5 用 $props() 替代 export let
  let { task, onDelete, onStatusChange }: {
    task: Task;
    onDelete: (id: number) => void;
    onStatusChange: (id: number, status: Task['status']) => void;
  } = $props();

  // 基于任务状态计算样式
  let statusColor = $derived(
    task.status === 'done' ? 'bg-green-100 text-green-800' :
    task.status === 'in-progress' ? 'bg-yellow-100 text-yellow-800' :
    'bg-gray-100 text-gray-800'
  );
</script>

<div class="rounded-lg border p-4 shadow-sm hover:shadow-md transition-shadow">
  <div class="flex items-center justify-between">
    <h3 class="font-semibold">{task.title}</h3>
    <span class="px-2 py-1 rounded-full text-xs {statusColor}">
      {task.status}
    </span>
  </div>
  {#if task.description}
    <p class="mt-2 text-sm text-gray-600">{task.description}</p>
  {/if}
  <div class="mt-3 flex gap-2">
    <select
      class="text-sm border rounded px-2 py-1"
      value={task.status}
      onchange={(e) => onStatusChange(task.id, e.currentTarget.value as Task['status'])}
    >
      <option value="todo">待办</option>
      <option value="in-progress">进行中</option>
      <option value="done">已完成</option>
    </select>
    <button
      class="text-sm text-red-500 hover:text-red-700"
      onclick={() => onDelete(task.id)}
    >
      删除
    </button>
  </div>
</div>

3.5 Snippets 与 {@render} — 替代 Slot

<!-- TaskList.svelte -->
<script lang="ts">
  import type { Task } from '$lib/server/db';

  let { tasks, children }: {
    tasks: Task[];
    children: import('svelte').Snippet<[{ task: Task; index: number }]>;
  } = $props();
</script>

{#if tasks.length === 0}
  <div class="text-center py-12 text-gray-400">
    暂无任务,创建第一个吧
  </div>
{:else}
  <div class="grid gap-3">
    {#each tasks as task, i (task.id)}
      {@render children({ task, index: i })}
    {/each}
  </div>
{/if}

使用方式:

<TaskList {filteredTasks}>
  {#snippet children({ task, index })}
    <TaskCard
      {task}
      onDelete={handleDelete}
      onStatusChange={handleStatusChange}
    />
  {/snippet}
</TaskList>

第四步:SvelteKit 路由与 Server Actions

4.1 项目路由结构

src/routes/
├── +layout.svelte          # 全局布局(导航栏、侧边栏)
├── +layout.server.ts       # 全局布局数据加载
├── +page.svelte            # 首页(任务看板)
├── +page.server.ts         # 首页数据加载 + Actions
├── tasks/
│   ├── [id]/
│   │   ├── +page.svelte    # 任务详情
│   │   └── +page.server.ts # 任务详情数据
│   └── +page.svelte        # 任务列表
└── api/
    └── tasks/
        └── +server.ts      # REST API(可选)

4.2 根布局

创建 src/routes/+layout.svelte:

<script lang="ts">
  import '../app.css';

  let { children } = $props();
</script>

<div class="min-h-screen bg-gray-50">
  <nav class="bg-white shadow-sm border-b">
    <div class="max-w-6xl mx-auto px-4 py-3 flex items-center justify-between">
      <a href="/" class="text-xl font-bold text-indigo-600">TaskFlow</a>
      <div class="flex gap-4 text-sm">
        <a href="/" class="hover:text-indigo-600">看板</a>
        <a href="/tasks" class="hover:text-indigo-600">列表</a>
      </div>
    </div>
  </nav>
  <main class="max-w-6xl mx-auto px-4 py-6">
    {@render children()}
  </main>
</div>

4.3 Server Actions — 表单驱动的数据变更

创建 src/routes/+page.server.ts:

import { getAllTasks, createTask, updateTask, deleteTask } from '$lib/server/db';
import { fail } from '@sveltejs kit';
import type { Actions, PageServerLoad } from './$types';

export const load: PageServerLoad = async () => {
  return {
    tasks: getAllTasks()
  };
};

export const actions: Actions = {
  create: async ({ request }) => {
    const data = await request.formData();
    const title = data.get('title')?.toString()?.trim();
    const description = data.get('description')?.toString()?.trim() ?? '';
    const priority = data.get('priority')?.toString() ?? 'medium';

    if (!title || title.length < 1) {
      return fail(400, { error: '任务标题不能为空' });
    }

    createTask({ title, description, priority: priority as 'low' | 'medium' | 'high' });
    return { success: true };
  },

  updateStatus: async ({ request }) => {
    const data = await request.formData();
    const id = Number(data.get('id'));
    const status = data.get('status')?.toString();

    if (!id || !status) {
      return fail(400, { error: '参数缺失' });
    }

    updateTask(id, { status: status as 'todo' | 'in-progress' | 'done' });
    return { success: true };
  },

  delete: async ({ request }) => {
    const data = await request.formData();
    const id = Number(data.get('id'));

    if (!id) {
      return fail(400, { error: '参数缺失' });
    }

    deleteTask(id);
    return { success: true };
  }
};

为什么用 Server Actions 而不是 REST API? Server Actions 让表单无需 JavaScript 就能工作(渐进增强),且自动处理 CSRF 防护。Claude Code 在 CLAUDE.md 中看到"Use form actions for mutations"指令后,会默认使用这种模式。

4.4 首页看板视图

创建 src/routes/+page.svelte:

<script lang="ts">
  import type { PageData, ActionData } from './$types';
  import TaskCard from '$lib/components/TaskCard.svelte';

  let { data, form }: { data: PageData; form: ActionData } = $props();

  // 看板分组
  let columns = $derived({
    '待办': data.tasks.filter(t => t.status === 'todo'),
    '进行中': data.tasks.filter(t => t.status === 'in-progress'),
    '已完成': data.tasks.filter(t => t.status === 'done'),
  });

  // 新建任务表单状态
  let showForm = $state(false);
  let newTitle = $state('');
  let newDescription = $state('');
  let newPriority = $state<'low' | 'medium' | 'high'>('medium');
</script>

<div class="flex items-center justify-between mb-6">
  <h1 class="text-2xl font-bold">任务看板</h1>
  <button
    class="bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700"
    onclick={() => showForm = !showForm}
  >
    {showForm ? '取消' : '+ 新任务'}
  </button>
</div>

{#if form?.error}
  <div class="bg-red-50 text-red-600 px-4 py-2 rounded mb-4">{form.error}</div>
{/if}

{#if showForm}
  <form method="POST" action="?/create" class="bg-white rounded-lg shadow p-6 mb-6">
    <div class="space-y-4">
      <div>
        <label class="block text-sm font-medium mb-1">任务标题</label>
        <input
          name="title"
          bind:value={newTitle}
          class="w-full border rounded-lg px-3 py-2"
          placeholder="输入任务标题..."
          required
        />
      </div>
      <div>
        <label class="block text-sm font-medium mb-1">描述</label>
        <textarea
          name="description"
          bind:value={newDescription}
          class="w-full border rounded-lg px-3 py-2"
          rows="3"
          placeholder="可选描述..."
        ></textarea>
      </div>
      <div>
        <label class="block text-sm font-medium mb-1">优先级</label>
        <select name="priority" bind:value={newPriority} class="border rounded-lg px-3 py-2">
          <option value="low">低</option>
          <option value="medium">中</option>
          <option value="high">高</option>
        </select>
      </div>
      <button type="submit" class="bg-indigo-600 text-white px-6 py-2 rounded-lg">
        创建任务
      </button>
    </div>
  </form>
{/if}

<!-- 看板列 -->
<div class="grid grid-cols-3 gap-6">
  {#each Object.entries(columns) as [name, tasks]}
    <div>
      <h2 class="font-semibold mb-3 flex items-center gap-2">
        {name}
        <span class="text-sm text-gray-400">({tasks.length})</span>
      </h2>
      <div class="space-y-3">
        {#each tasks as task (task.id)}
          <form method="POST" action="?/updateStatus">
            <input type="hidden" name="id" value={task.id} />
            <TaskCard
              {task}
              onDelete={(id) => {
                // 使用表单提交删除
                const form = document.createElement('form');
                form.method = 'POST';
                form.action = '?/delete';
                const input = document.createElement('input');
                input.type = 'hidden';
                input.name = 'id';
                input.value = String(id);
                form.appendChild(input);
                document.body.appendChild(form);
                form.submit();
              }}
              onStatusChange={(id, status) => {
                const input = document.createElement('input');
                input.type = 'hidden';
                input.name = 'status';
                input.value = status;
                // 找到最近的 form 并添加 status 字段后提交
                const card = document.querySelector(`[data-task-id="${id}"]`);
                card?.closest('form')?.appendChild(input);
                card?.closest('form')?.submit();
              }}
            />
          </form>
        {/each}
      </div>
    </div>
  {/each}
</div>

第五步:Claude Code 协作模式深度实战

5.1 分步开发策略

不要一次性描述整个应用。将需求拆分为 Claude Code 可处理的小块:

flowchart TD
    A[需求描述] --> B[第1轮:数据库层]
    B --> C[第2轮:路由与数据加载]
    C --> D[第3轮:UI组件]
    D --> E[第4轮:Server Actions]
    E --> F[第5轮:样式与交互]
    F --> G[第6轮:部署配置]

    style A fill:#6366f1,color:#fff
    style G fill:#22c55e,color:#fff

每一轮的提示词模板:

## 当前状态
已完成:[已完成的模块]
进行中:[当前模块]

## 需求
[具体描述]

## 约束
- 使用 Svelte 5 Runes 语法
- TypeScript 类型安全
- 遵循 CLAUDE.md 中的文件结构

5.2 让 Claude Code 生成组件脚手架

请在 src/lib/components/ 下创建以下组件,每个组件使用 Svelte 5 Runes 语法:

1. TaskCard.svelte — 任务卡片,接收 task prop,支持状态变更和删除
2. TaskForm.svelte — 新建/编辑任务表单,使用 $bindable 实现双向绑定
3. TaskBoard.svelte — 看板布局,三列分组显示任务
4. StatusBadge.svelte — 状态标签,根据状态显示不同颜色
5. PriorityIcon.svelte — 优先级图标

所有组件的 props 使用 $props() rune,不要使用 export let。

5.3 调试 Svelte 5 常见问题

当遇到 Svelte 5 相关错误时,告诉 Claude Code:

我遇到了以下错误:
[粘贴错误信息]

请检查:
1. 是否有 Svelte 4 语法混入($:、$store、on:click、<slot>)
2. $state/$derived 是否在 .svelte 文件的 <script> 顶层使用
3. $effect 中是否有异步操作未正确处理

常见 Svelte 5 错误对照表:

错误信息 原因 修复
Cannot use $state outside a .svelte file Runes 只能在 .svelte 文件顶层使用 改用 .svelte.ts 文件中的 class
$derived cannot be used with assignments $derived 是只读的 改用 $state + $effect
on:click is not a valid attribute Svelte 4 事件语法 改为 onclick={handler}
Unexpected token in slot Svelte 5 移除了 <slot> 使用 {#snippet} + {@render}

5.4 用 .svelte.ts 共享响应式逻辑

当需要在多个组件间共享状态时,使用 .svelte.ts 文件:

// src/lib/stores/task-store.svelte.ts

import { getAllTasks, createTask, updateTask, deleteTask } from '$lib/server/db';
import type { Task } from '$lib/server/db';

// 使用 class 封装响应式状态(Svelte 5 推荐模式)
class TaskStore {
  tasks = $state<Task[]>([]);
  loading = $state(false);
  error = $state<string | null>(null);

  // $derived 自动计算
  get stats() {
    return {
      total: this.tasks.length,
      todo: this.tasks.filter(t => t.status === 'todo').length,
      done: this.tasks.filter(t => t.status === 'done').length,
    };
  }

  // 方法可以放在 class 内
  filterByStatus(status: Task['status'] | 'all') {
    if (status === 'all') return this.tasks;
    return this.tasks.filter(t => t.status === status);
  }
}

export const taskStore = new TaskStore();

注意:在 .svelte.ts 文件中使用 runes 时,需要用 class + get 模式替代 $derived,因为顶层 runes 在非 .svelte 文件中受限。


第六步:完整实战 — 任务详情页与高级交互

6.1 动态路由

创建 src/routes/tasks/[id]/+page.server.ts:

import { getTaskById } from '$lib/server/db';
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ params }) => {
  const task = getTaskById(Number(params.id));

  if (!task) {
    throw error(404, '任务不存在');
  }

  return { task };
};

创建 src/routes/tasks/[id]/+page.svelte:

<script lang="ts">
  import type { PageData } from './$types';

  let { data }: { data: PageData } = $props();

  // 编辑模式
  let editing = $state(false);
  let editTitle = $state(data.task.title);
  let editDescription = $state(data.task.description);

  // 监听数据变化重置编辑状态
  $effect(() => {
    editTitle = data.task.title;
    editDescription = data.task.description;
  });
</script>

<div class="max-w-2xl mx-auto">
  <a href="/" class="text-indigo-600 hover:underline mb-4 inline-block">
    ← 返回看板
  </a>

  {#if editing}
    <form method="POST" action="?/update" class="bg-white rounded-lg shadow p-6">
      <input type="hidden" name="id" value={data.task.id} />
      <div class="space-y-4">
        <input name="title" bind:value={editTitle} class="w-full border rounded px-3 py-2 text-xl font-bold" />
        <textarea name="description" bind:value={editDescription} class="w-full border rounded px-3 py-2" rows="6"></textarea>
        <div class="flex gap-3">
          <button type="submit" class="bg-indigo-600 text-white px-4 py-2 rounded">保存</button>
          <button type="button" class="border px-4 py-2 rounded" onclick={() => editing = false}>取消</button>
        </div>
      </div>
    </form>
  {:else}
    <div class="bg-white rounded-lg shadow p-6">
      <div class="flex items-center justify-between mb-4">
        <h1 class="text-2xl font-bold">{data.task.title}</h1>
        <button class="text-indigo-600" onclick={() => editing = true}>编辑</button>
      </div>
      <p class="text-gray-600">{data.task.description || '暂无描述'}</p>
      <div class="mt-4 flex gap-4 text-sm text-gray-400">
        <span>优先级: {data.task.priority}</span>
        <span>创建于: {new Date(data.task.created_at).toLocaleDateString('zh-CN')}</span>
      </div>
    </div>
  {/if}
</div>

6.2 添加过渡动画

Svelte 内置过渡系统:

<script lang="ts">
  import { fly, fade } from 'svelte/transition';
</script>

{#each tasks as task (task.id)}
  <div transition:fly={{ y: 20, duration: 300 }}>
    <TaskCard {task} />
  </div>
{/each}

第七步:完整工作流架构图

flowchart TB
    subgraph Client["浏览器端 (Svelte 5 Runes)"]
        A[$state — 响应式状态] --> B[$derived — 派生计算]
        B --> C[$effect — 副作用]
        D[$props — 组件属性] --> E["{#snippet} + {@render}"]
    end

    subgraph SvelteKit["SvelteKit 路由层"]
        F["+page.server.ts<br/>Load 函数"] --> G["+page.svelte<br/>UI 渲染"]
        H["Form Actions<br/>create / update / delete"] --> I["Server-only<br/>数据变更"]
    end

    subgraph Server["服务端 (Node.js)"]
        J["db.ts<br/>better-sqlite3"] --> K["schema.sql<br/>SQLite 数据库"]
        L["$lib/server/<br/>服务端代码"] --> J
    end

    Client -->|HTTP Request| SvelteKit
    SvelteKit -->|SQL Query| Server
    Server -->|JSON Response| SvelteKit
    SvelteKit -->|HTML / Data| Client

    style Client fill:#6366f1,color:#fff
    style SvelteKit fill:#f59e0b,color:#fff
    style Server fill:#22c55e,color:#fff
sequenceDiagram
    participant U as 用户
    participant P as +page.svelte
    participant PS as +page.server.ts
    participant DB as SQLite

    U->>P: 访问首页
    P->>PS: load() 请求数据
    PS->>DB: getAllTasks()
    DB-->>PS: Task[]
    PS-->>P: { tasks: Task[] }
    P-->>U: 渲染看板

    U->>P: 填写新任务表单
    U->>P: 点击"创建"
    P->>PS: POST ?/create
    PS->>DB: createTask(data)
    DB-->>PS: 新 Task
    PS-->>P: { success: true }
    PS->>DB: getAllTasks()(自动重新加载)
    DB-->>PS: Task[]
    PS-->>P: 更新后的 { tasks }
    P-->>U: 看板更新(无刷新)

第八步:部署配置

8.1 Vercel 部署

npm install -D @sveltejs/adapter-vercel

编辑 svelte.config.js:

import adapter from '@sveltejs/adapter-vercel';

export default {
  kit: {
    adapter: adapter({
      runtime: 'nodejs20.x',
      regions: ['hnd1']  // 东京区域,中国访问较快
    })
  }
};

8.2 让 Claude Code 配置部署

提示词:

请将本项目配置为 Vercel 部署:
1. 安装并配置 adapter-vercel
2. 确保 svelte.config.js 正确
3. 添加 vercel.json(如果需要)
4. 确认构建命令 npm run build 可正常运行

8.3 部署检查清单

让 Claude Code 执行完整检查:

请执行以下部署前检查:
1. npm run build 是否成功
2. 是否有 TypeScript 类型错误
3. 是否有 ESLint 警告
4. 环境变量是否需要配置
5. .env 文件是否在 .gitignore 中

第九步:Claude Code + Svelte 5 高级技巧

9.1 用 Claude Code 批量迁移 Svelte 4 到 Svelte 5

如果已有 Svelte 4 项目:

请帮我将本项目从 Svelte 4 迁移到 Svelte 5。按照以下规则逐文件修改:

1. `let x = 0` → `let x = $state(0)`
2. `$: doubled = x * 2` → `let doubled = $derived(x * 2)`
3. `export let prop` → `let { prop } = $props()`
4. `on:click={handler}` → `onclick={handler}`
5. `<slot />` → `{@render children()}`
6. `createEventDispatcher` → 直接使用回调函数 props
7. `writable/readable stores` → `.svelte.ts` 中的 class

每次只修改一个文件,修改后运行 npm run build 确认无报错。

9.2 上下文管理技巧

对于大型 Svelte 项目,上下文窗口管理至关重要:

  1. 分模块开发:每次只聚焦一个路由或组件,不要让 Claude Code 一次读取全部文件
  2. 使用 .claude/rules/ 目录:将 Svelte 5 语法规则放在独立的 rule 文件中
  3. 利用 /compact:当上下文接近上限时,执行 /compact 压缩历史对话

创建 .claude/rules/svelte5.md:

# Svelte 5 编码规则

## Runes 语法
- 状态声明:`let x = $state(initialValue)`
- 派生计算:`let y = $derived(expression)`
- 副作用:`$effect(() => { ... })`
- 组件属性:`let { a, b } = $props()`

## 事件处理
- 使用 `onclick={handler}` 而非 `on:click={handler}`
- 使用 `onchange`、`oninput`、`onsubmit` 等

## 模板
- 条件渲染:`{#if}...{:else}...{/if}`
- 循环:`{#each items as item (item.id)}...{/each}`
- Snippets:`{#snippet name(args)}...{/snippet}`
- 渲染:`{@render name(args)}`

9.3 实用 Claude Code 提示词模板

场景 提示词
创建新组件 请创建 [组件名].svelte,使用 Svelte 5 $props rune,功能是 [描述]
修复 Runes 错误 这个 $derived 报错 [错误信息],请检查是否 [可能原因]
添加 Server Action 在 +page.server.ts 中添加 [action名] action,处理 [逻辑]
样式调整 请用 Tailwind CSS 优化 [组件名] 的响应式布局
性能优化 分析 [组件名] 的渲染性能,是否有不必要的 $effect 或 $derived

延伸阅读


训练营适用性评估

维度 评估
适合类型 前端开发训练营、全栈工程师速成班、AI 辅助开发工作坊
教学时长 3-4 小时(含实操练习)
难度曲线 中等 — 需要前端基础,Svelte 5 新概念有学习门槛但曲线平缓
扩展方向 可扩展为:添加用户认证(Lucia)、实时协作(WebSocket)、PWA 离线支持
建议分组 2-3 人一组,每组完成不同功能模块后合并

参考来源

  1. Svelte 5 Runes: The Complete Guide — Full Stack SvelteKit — Runes 系统 $state/$derived/$effect/$props 完整解析
  2. Claude Code Best Practices — Anthropic Docs — 上下文管理、CLAUDE.md 配置官方最佳实践
  3. SvelteKit Form Actions — Svelte Docs — Server Actions 表单处理官方文档
  4. Claude Instructions for Svelte Developers — Dejan Vasic — CLAUDE.md Svelte 5 配置实战经验
  5. Svelte 5 Migration Guide — Svelte Docs — Svelte 4 到 5 的语法差异与迁移步骤

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入