mcpField
S
svelte-kit

svelte-kit

github.com/tomlord1122开发058 次安装2 天前更新
代码仓库

关于 svelte-kit

面向 Svelte 和 SvelteKit 开发者的开发技能,用于处理实现问题。它侧重 Svelte 5 runes 与现代 SvelteKit 模式,并结合版本、渲染、路由和数据加载情境提供指导。

主要功能

  1. 01Svelte 5 runes 指导
  2. 02SvelteKit 路由与加载模式
  3. 03SSR 与水合分析
  4. 04组件设计模式
  5. 05按版本提供建议

适用场景

  1. 01排查响应式状态问题
  2. 02选择服务端或通用加载函数
  3. 03避免 SSR 与水合问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/tomlord1122/tomtom-skill --skill svelte-kit

安装后在 Claude Code 中输入/svelte-kit确认已加载。

详细介绍

Svelte/SvelteKit 专家

专注于 Svelte 5 runes 语法、SvelteKit 路由、SSR/SSG 策略和组件设计模式的专家助手。

思维转变(Svelte 4 → 5)

默认采用 Svelte 5 runes 和现代 SvelteKit 惯例。最重要的思维转变:

  • Stores → runes。 优先使用 $state/$derived,而不是 writable/$:。$state 对象/数组具有深层响应性,是 代理(proxy)。
  • $effect 是最后的选择,不是同步工具。 使用 $derived 派生值;仅在确实需要副作用时(DOM、日志记录、订阅)使用 $effect。
  • Slots → snippets。 {#snippet} / {@render} 取代 <slot>。
  • on:click → onclick。 事件现在是普通属性(不再使用冒号)。
  • $app/stores → $app/state。 粒度更细,无需 $ 前缀。
  • export let → $props(),并从 $types 导入 PageProps/LayoutProps。

思考过程

激活后,按照以下结构化思考方法解决 Svelte/SvelteKit 问题:

步骤 1:问题分类

目标: 理解这是哪一类 Svelte 问题。

需要提出的关键问题:

  • 这是响应式问题吗?(状态更新未反映、派生值)
  • 这是渲染问题吗?(SSR 与 CSR、水合不匹配)
  • 这是路由问题吗?(导航、参数、布局)
  • 这是数据加载问题吗?(load 函数、表单操作)
  • 这是组件设计问题吗?(属性、插槽、事件)

决策点: 对问题进行分类,以选择合适的解决方案:

  • 响应式 → 检查 runes 的用法($state、$derived、$effect)
  • 渲染 → 考虑 SSR/CSR 的影响
  • 路由 → 查看 SvelteKit 惯例
  • 数据加载 → 区分 +page.ts 与 +page.server.ts
  • 组件 → 应用组合模式
步骤 2:版本与上下文检查

目标: 确保解决方案符合项目使用的 Svelte 版本。

需要提出的关键问题:

  • 使用的是 Svelte 5(runes)还是 Svelte 4(stores)?
  • 使用的是哪个版本的 SvelteKit?
  • 配置了哪种渲染模式?(SSR、SPA、SSG)

操作:

  1. 检查 package.json 中的 svelte 和 @sveltejs/kit 版本
  2. 查找 svelte.config.js 中的适配器配置
  3. 留意任何预渲染设置

特定版本语法:

概念Svelte 4Svelte 5
响应式状态let x = 0let x = $state(0)
派生值$: doubled = x * 2let doubled = $derived(x * 2)
副作用$: console.log(x)$effect(() => console.log(x))
属性export let namelet { name } = $props()

决策点: 除非明确是在使用 Svelte 4,否则始终默认采用 Svelte 5 runes 语法。

步骤 3:SSR/CSR 分析

目标: 理解渲染上下文及其影响。

思考框架:

  • “这段代码何时运行?”(服务端、客户端或两者)
  • “每个阶段能访问哪些数据?”
  • “这会导致水合不匹配吗?”

SSR 决策矩阵:

代码位置运行位置用途
+page.server.ts仅服务端数据库访问、机密信息、身份验证
+page.ts服务端 + 客户端公共 API 调用、依赖 URL 的数据
+page.svelte服务端 + 客户端UI 渲染
$effect()仅客户端DOM 操作、订阅

常见 SSR 陷阱:

  • 在 SSR 上下文中使用浏览器 API(window、document)
  • 服务端与客户端渲染的内容不同
  • 错误地访问 cookie/请求头

SSR 安全模式: 使用 import { browser } from '$app/environment' 保护仅限浏览器的 API,并在 $effect(() => { if (browser) { /* ... */ } }) 中执行 DOM 访问(effects 仅在客户端运行)。

步骤 4:数据流设计

目标: 设计正确的数据加载和变更模式。

思考框架:

  • “这些数据来自哪里?”(服务端、客户端、URL)
  • “什么时候应该获取这些数据?”(导航、操作、间隔)
  • “谁可以访问这些数据?”(公开、已认证、已授权)

加载函数选择:

需求使用原因
访问机密信息/数据库+page.server.ts永远不会暴露给客户端
调用公开 API+page.ts两端都会运行,适合缓存
对 SEO 至关重要的数据+page.server.ts保证包含在初始 HTML 中
仅在客户端使用在 $effect 中执行 fetch避免 SSR 开销

表单操作思考:

  • “此表单执行什么变更?”
  • “需要进行哪些验证?”
  • “成功/失败时应该如何处理?”

加载重新运行与身份验证影响:

  • 使用 invalidate(url) / invalidateAll() 重新运行加载;使用 depends() 声明依赖项;使用 untrack() 选择退出跟踪。
  • 客户端导航时,布局加载不会自动重新运行。 对于身份验证,优先使用 handle 钩子或单页服务器加载,而不是仅使用根布局加载。
  • 操作更改身份验证状态时,更新 event.locals,以便后续加载获取新状态。
  • 在共享服务器函数中使用 getRequestEvent()(SvelteKit ≥ 2.20)读取 locals/url,无需通过参数传递事件。

流式传输: 从服务器加载中返回一个未等待的 promise,并使用 {#await} 渲染,以渐进式显示数据。

步骤 5:响应式设计

目标: 根据使用场景应用正确的响应式模式。

思考框架 — Runes 选择:

需求Rune示例
可变状态$statelet count = $state(0)
计算值$derivedlet double = $derived(count * 2)
副作用$effect$effect(() => save(data))
组件属性$propslet { name } = $props()
双向绑定$bindablelet { value = $bindable() } = $props()

响应式规则:

  1. 仅对需要触发更新的值使用 $state
  2. 对所有计算值使用 $derived(不要手动更新);保持派生表达式纯净(无副作用)。对于多行逻辑,使用 $derived.by(() => { ... })。
  3. 谨慎使用 $effect — 仅用于真正的副作用(DOM、日志记录、订阅),绝不要用它来同步/派生状态。返回一个清理函数以进行清理。
  4. 绝不要通过 $effect 修改 $derived 值来“修复”它们。派生值可以重新赋值以实现乐观 UI,并会在依赖项更改时自动恢复。

深层响应性与状态细节:

  • $state 对象/数组是深度响应式的代理 — 修改嵌套字段会触发更新。
  • 绝不要解构响应式状态或属性 — 这会捕获快照并破坏响应性。直接访问字段(obj.x)或使用 getter。
  • $state.raw(...) — 浅层/不跟踪的状态;重新赋值整个值,不要进行修改。
  • $state.snapshot(...) — 用于传递给外部 API/非 Svelte API 的普通(非代理)副本。
  • 通过getter 函数(() => count)向函数传递实时状态,而不是直接传递变量。

Effect 变体: $effect.pre(在 DOM 更新前运行,例如自动滚动)、$effect.tracking()(是否处于响应式上下文中?)、$effect.root(手动作用域,手动清理)。

应避免的反模式:

<script>
  // BAD: using $effect to derive/sync state
  let doubled = $state(0);
  $effect(() => { doubled = count * 2; }); // use $derived instead

  // BAD: destructuring reactive state/props (loses reactivity)
  let { user } = data;        // snapshot — won't update
  let { x, y } = $state({ x: 0, y: 0 });

  // BAD: mutating props
  let { count } = $props();
  count += 1;                 // props are read-only; use $bindable() for two-way
</script>
步骤 6:组件设计

目标: 设计可复用、可组合的组件。

思考框架:

  • “此组件的单一职责是什么?”
  • “它需要哪些属性?”
  • “插槽组合应有多大的灵活性?”

组件接口设计:

<script>
  // Required props
  let { title, items } = $props();

  // Optional props with defaults
  let { variant = 'default', disabled = false } = $props();

  // Callback props
  let { onClick = () => {} } = $props();

  // Bindable props for two-way binding
  let { value = $bindable() } = $props();
</script>

插槽模式:

  • 默认插槽:主要内容区域
  • 命名插槽:页眉、页脚、侧边栏
  • 插槽属性:向插槽内容传递数据
第 7 步:性能优化

目标: 确保渲染性能达到最佳。

思考框架:

  • “这个响应式值多久变化一次?”
  • “重新渲染的成本有多高?”
  • “能否使用记忆化或防抖?”

性能检查清单:

  • 避免在 $derived 中执行开销大的计算
  • 使用 {#key} 块强制重新渲染
  • 为长列表实现虚拟化
  • 延迟加载大型组件
  • 预加载关键路由
第 8 步:错误处理

目标: 提供良好的错误体验。

错误边界: +error.svelte(路由级)、load 函数中的 try/catch、 表单操作中的 fail()、<svelte:boundary>(组件级,参见快速参考)。

错误模式: 当数据缺失时,从 load 中抛出 error(404, 'Not found'); SvelteKit 会使用 page.error/page.status 渲染最近的 +error.svelte。

项目设置

首选包管理器: bun

# Create new SvelteKit project with the modern `sv` CLI
# (replaces the deprecated `npm create svelte`)
bunx sv create my-app
cd my-app
bun install
bun run dev

精简的现代依赖项(均为 devDependencies):@sveltejs/adapter-auto ^6、@sveltejs/kit ^2、@sveltejs/vite-plugin-svelte ^5、svelte ^5、vite ^6。

文档资源

Context7 库 ID: /websites/svelte_dev(5523 个片段,评分:91)

AI 使用指南: https://svelte.dev/docs/ai

官方 llms.txt 资源(包级文件;旧的顶层 /docs/llms-small.txt 返回 404):

  • https://svelte.dev/llms.txt - 文档索引
  • https://svelte.dev/docs/svelte/llms-full.txt - 完整的 Svelte 文档
  • https://svelte.dev/docs/svelte/llms-small.txt - 压缩版 Svelte 文档
  • https://svelte.dev/docs/kit/llms.txt - SvelteKit 文档

快速参考

Svelte 5 Runes
<script>
  // Reactive state
  let count = $state(0);

  // Derived values (auto-updates when dependencies change)
  let doubled = $derived(count * 2);

  // Side effects
  $effect(() => {
    console.log(`Count is now ${count}`);
  });

  // Multi-line derived
  let summary = $derived.by(() => items.reduce((a, b) => a + b.n, 0));

  // Props: defaults, rename, rest
  let { name = 'World', super: trouper, ...rest } = $props();

  // Bindable props (opt-in two-way binding; default to one-way)
  let { value = $bindable() } = $props();

  // SSR-safe unique id
  const id = $props.id();
</script>
现代模板语法
<script>
  import type { Snippet } from 'svelte';
  let { header, children }: { header?: Snippet; children: Snippet } = $props();
  let active = $state(true);
</script>


<button onclick={() => (active = !active)}>Toggle</button>


<div class={{ active, disabled: !active }}>...</div>


{#snippet row(label, value)}
  <tr><th>{label}</th><td>{value}</td></tr>
{/snippet}

{@render header?.()}
{@render children()}
{@render row('Status', active ? 'on' : 'off')}


<svelte:boundary>
  <RiskyComponent />
  {#snippet failed(error, reset)}
    <p>{error.message}</p>
    <button onclick={reset}>Retry</button>
  {/snippet}
</svelte:boundary>
SvelteKit 路由

src/routes/ 下的文件:+page.svelte(UI)、+page.ts(通用 load)、 +page.server.ts(服务器 load/操作)、+layout.svelte / +layout(.server).ts (布局)、+server.ts(API 端点)、+error.svelte(错误边界)。 动态片段使用 [slug];剩余参数使用 [...rest];可选参数使用 [[lang]]。

Load 函数与类型化数据
// +page.server.ts - Server-only
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params, locals, fetch }) => {
  const post = await fetch(`/api/posts/${params.slug}`);
  return { post: await post.json() };
};

<script lang="ts">
  import type { PageProps } from './$types';
  let { data }: PageProps = $props();
</script>
页面状态(使用 $app/state,而非 $app/stores)
<script>
  // `$app/stores` is deprecated. `$app/state` is fine-grained, no `$` prefix.
  import { page, navigating, updated } from '$app/state';
</script>

<p>Path: {page.url.pathname} · Status: {page.status}</p>
{#if navigating.to}<progress></progress>{/if}
表单操作
// +page.server.ts
import { fail } from '@sveltejs/kit';
import type { Actions } from './$types';

export const actions: Actions = {
  default: async ({ request, locals }) => {
    const data = await request.formData();
    const email = data.get('email');
    // Use fail() for validation errors — do NOT throw
    if (!email) return fail(400, { email, missing: true });
    return { success: true };
  }
};

<script>
  import { enhance } from '$app/forms';
</script>
<form method="POST" use:enhance>...</form>
页面选项
// +page.ts / +page.server.ts
export const prerender = true;   // or false | 'auto'
export const ssr = false;        // SPA shell (client-rendered)
export const csr = false;        // static HTML, no JS/hydration

// Dynamic prerender entries
export function entries() {
  return [{ slug: 'first' }, { slug: 'second' }];
}
Hooks
Hook文件作用范围用途
handle / handleFetch / inithooks.server服务器请求拦截、身份验证、fetch 重写、启动
handleErrorhooks (.server/.client)共享集中式错误记录/整形
reroute / transporthooks通用URL 重写;在服务器↔客户端之间对自定义类型进行(反)序列化

状态管理

  • 服务器是无状态的——切勿在模块级全局变量中存储每个用户的数据(这会导致数据泄漏到其他请求中)。保持 load 函数纯净。
  • 通过上下文 API(setContext/getContext)、URL 参数或快照共享客户端状态,而不要使用模块级可变单例。

现代模式(实验性——需选择启用)

这些 API 均为实验性。请显式启用;除非项目选择启用这些功能,否则优先使用上文稳定的 runes/load/action 模式。

异步 Svelte

在 Svelte 5.36 中为实验性功能(标志已移除/在 Svelte 6 中默认启用)。允许在顶层 <script>、$derived(...) 内以及标记中直接使用 await。

// svelte.config.js
export default { compilerOptions: { experimental: { async: true } } };
<script>
  let { id } = $props();
  // await directly in $derived
  let post = $derived(await getPost(id));
</script>


<svelte:boundary>
  <h1>{post.title}</h1>
  {#snippet pending()}<p>loading…</p>{/snippet}
</svelte:boundary>
  • 使用 $effect.pending() 检测正在进行的异步操作。
  • 当独立的异步工作被不必要地串行执行时,会触发 await_waterfall 警告——应同时启动相互独立的工作。
  • 错误会向上冒泡至最近的 <svelte:boundary>。
SvelteKit 远程函数

可选启用的类型安全服务器函数,可从客户端调用;可以替代 load 函数和表单操作。与异步 Svelte 自然搭配。

// svelte.config.js
export default { kit: { experimental: { remoteFunctions: true } } };

.remote.ts 文件从 $app/server 导出:

导出项用途亮点
query读取动态数据按页面缓存、.refresh()、Standard Schema(Zod/Valibot)校验
form表单变更将 {...createPost} 展开到 <form> 上;渐进增强;单航班 .updates();通过 withOverride 实现乐观 UI;每个按钮的 buttonProps
command编程式写入.updates(query());渲染期间不能运行;不支持重定向
prerender构建时读取inputs: 种子;{ dynamic: true } 选项
// data.remote.ts
import { query } from '$app/server';
import * as v from 'valibot';

export const getPost = query(v.string(), async (slug) => {
  return await db.posts.find(slug); // server-only, type-safe end to end
});
  • 校验失败时返回 400;可通过 handleValidationError 自定义。
  • getRequestEvent() 可在远程函数中使用(没有 params/route.id;只能在 form/command 中设置 Cookie)。

向用户展示结果

回答 Svelte/SvelteKit 问题时:

  • 提供完整、可运行的代码示例
  • 默认使用 Svelte 5 runes 语法和现代 SvelteKit 惯例($app/state、PageProps、snippets)
  • 说明服务器 load 函数与通用 load 函数之间的区别
  • 指出 SvelteKit 版本之间的破坏性变更
  • 标明实验性 API(异步 Svelte、远程函数)需要选择启用
  • 适用时包含 TypeScript 类型

故障排查

“Cannot use $state outside of component”

  • Runes 仅适用于 .svelte 文件或 .svelte.ts 文件

“Hydration mismatch”

  • 确保服务器和客户端初始渲染的内容相同
  • 检查是否有仅限浏览器的代码在 SSR 期间运行

“Load function not running”

  • 确认文件名:+page.ts 或 +page.server.ts
  • 检查是否正确导出了 load 函数

评论

支持 Markdown