sveltekit

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SvelteKit Full-Stack Development

SvelteKit全栈开发

Overview

概述

SvelteKit is the official full-stack framework built on top of Svelte. It provides file-based routing, server-side rendering (SSR), static site generation (SSG), API routes, and progressive form actions — all with Svelte's compile-time reactivity model that ships zero runtime overhead to the browser. Use this skill when building fast, modern web apps where both DX and performance matter.
SvelteKit是基于Svelte构建的官方全栈框架。它提供基于文件的路由、服务器端渲染(SSR)、静态站点生成(SSG)、API路由和渐进式表单操作——所有这些都采用Svelte的编译时响应式模型,不会向浏览器发送任何运行时开销。当构建注重开发者体验(DX)和性能的快速现代Web应用时,请使用此技能。

When to Use This Skill

何时使用此技能

  • Use when building a new full-stack web application with Svelte
  • Use when you need SSR or SSG with fine-grained control per route
  • Use when migrating a SPA to a framework with server capabilities
  • Use when working on a project that needs file-based routing and collocated API endpoints
  • Use when the user asks about
    +page.svelte
    ,
    +layout.svelte
    ,
    load
    functions, or form actions
  • 当使用Svelte构建新的全栈Web应用时
  • 当你需要对每个路由进行细粒度控制的SSR或SSG时
  • 当将单页应用(SPA)迁移到具备服务器能力的框架时
  • 当处理需要基于文件的路由和并置API端点的项目时
  • 当用户询问
    +page.svelte
    +layout.svelte
    load
    函数或表单操作时

How It Works

工作原理

Step 1: Project Setup

步骤1:项目搭建

bash
npm create svelte@latest my-app
cd my-app
npm install
npm run dev
Choose Skeleton project + TypeScript + ESLint/Prettier when prompted.
Directory structure after scaffolding:
src/
  routes/
    +page.svelte        ← Root page component
    +layout.svelte      ← Root layout (wraps all pages)
    +error.svelte       ← Error boundary
  lib/
    server/             ← Server-only code (never bundled to client)
    components/         ← Shared components
  app.html              ← HTML shell
static/                 ← Static assets
bash
npm create svelte@latest my-app
cd my-app
npm install
npm run dev
提示时选择Skeleton project + TypeScript + ESLint/Prettier
搭建后的目录结构:
src/
  routes/
    +page.svelte        ← 根页面组件
    +layout.svelte      ← 根布局(包裹所有页面)
    +error.svelte       ← 错误边界
  lib/
    server/             ← 仅服务器端代码(永远不会打包到客户端)
    components/         ← 共享组件
  app.html              ← HTML外壳
static/                 ← 静态资源

Step 2: File-Based Routing

步骤2:基于文件的路由

Every
+page.svelte
file in
src/routes/
maps directly to a URL:
src/routes/+page.svelte          → /
src/routes/about/+page.svelte    → /about
src/routes/blog/[slug]/+page.svelte  → /blog/:slug
src/routes/shop/[...path]/+page.svelte → /shop/* (catch-all)
Route groups (no URL segment): wrap in
(group)/
folder. Private routes (not accessible as URLs): prefix with
_
or
(group)
.
src/routes/
中的每个
+page.svelte
文件直接映射到一个URL:
src/routes/+page.svelte          → /
src/routes/about/+page.svelte    → /about
src/routes/blog/[slug]/+page.svelte  → /blog/:slug
src/routes/shop/[...path]/+page.svelte → /shop/*(通配捕获)
路由组(无URL段):包裹在
(group)/
文件夹中。 私有路由(无法通过URL访问):前缀为
_
(group)

Step 3: Loading Data with
load
Functions

步骤3:使用
load
函数加载数据

Use a
+page.ts
(universal) or
+page.server.ts
(server-only) file alongside the page:
typescript
// src/routes/blog/[slug]/+page.server.ts
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ params, fetch }) => {
  const post = await fetch(`/api/posts/${params.slug}`).then(r => r.json());

  if (!post) {
    error(404, 'Post not found');
  }

  return { post };
};
svelte
<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
  import type { PageData } from './$types';
  export let data: PageData;
</script>

<h1>{data.post.title}</h1>
<article>{@html data.post.content}</article>
在页面旁创建
+page.ts
(通用)或
+page.server.ts
(仅服务器端)文件:
typescript
// src/routes/blog/[slug]/+page.server.ts
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ params, fetch }) => {
  const post = await fetch(`/api/posts/${params.slug}`).then(r => r.json());

  if (!post) {
    error(404, 'Post not found');
  }

  return { post };
};
svelte
<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
  import type { PageData } from './$types';
  export let data: PageData;
</script>

<h1>{data.post.title}</h1>
<article>{@html data.post.content}</article>

Step 4: API Routes (Server Endpoints)

步骤4:API路由(服务器端点)

Create
+server.ts
files for REST-style endpoints:
typescript
// src/routes/api/posts/+server.ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const GET: RequestHandler = async ({ url }) => {
  const limit = Number(url.searchParams.get('limit') ?? 10);
  const posts = await db.post.findMany({ take: limit });
  return json(posts);
};

export const POST: RequestHandler = async ({ request }) => {
  const body = await request.json();
  const post = await db.post.create({ data: body });
  return json(post, { status: 201 });
};
创建
+server.ts
文件以实现REST风格的端点:
typescript
// src/routes/api/posts/+server.ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const GET: RequestHandler = async ({ url }) => {
  const limit = Number(url.searchParams.get('limit') ?? 10);
  const posts = await db.post.findMany({ take: limit });
  return json(posts);
};

export const POST: RequestHandler = async ({ request }) => {
  const body = await request.json();
  const post = await db.post.create({ data: body });
  return json(post, { status: 201 });
};

Step 5: Form Actions

步骤5:表单操作

Form actions are the SvelteKit-native way to handle mutations — no client-side fetch required:
typescript
// src/routes/contact/+page.server.ts
import { fail, redirect } from '@sveltejs/kit';
import type { Actions } from './$types';

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

    if (!email) {
      return fail(400, { email, missing: true });
    }

    await sendEmail(String(email));
    redirect(303, '/thank-you');
  }
};
svelte
<!-- src/routes/contact/+page.svelte -->
<script lang="ts">
  import { enhance } from '$app/forms';
  import type { ActionData } from './$types';
  export let form: ActionData;
</script>

<form method="POST" use:enhance>
  <input name="email" type="email" />
  {#if form?.missing}<p class="error">Email is required</p>{/if}
  <button type="submit">Subscribe</button>
</form>
表单操作是SvelteKit原生的突变处理方式——无需客户端fetch:
typescript
// src/routes/contact/+page.server.ts
import { fail, redirect } from '@sveltejs/kit';
import type { Actions } from './$types';

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

    if (!email) {
      return fail(400, { email, missing: true });
    }

    await sendEmail(String(email));
    redirect(303, '/thank-you');
  }
};
svelte
<!-- src/routes/contact/+page.svelte -->
<script lang="ts">
  import { enhance } from '$app/forms';
  import type { ActionData } from './$types';
  export let form: ActionData;
</script>

<form method="POST" use:enhance>
  <input name="email" type="email" />
  {#if form?.missing}<p class="error">邮箱是必填项</p>{/if}
  <button type="submit">订阅</button>
</form>

Step 6: Layouts and Nested Routes

步骤6:布局与嵌套路由

svelte
<!-- src/routes/+layout.svelte -->
<script lang="ts">
  import type { LayoutData } from './$types';
  export let data: LayoutData;
</script>

<nav>
  <a href="/">Home</a>
  <a href="/blog">Blog</a>
  {#if data.user}
    <a href="/dashboard">Dashboard</a>
  {/if}
</nav>

<slot />  <!-- child page renders here -->
typescript
// src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types';

export const load: LayoutServerLoad = async ({ locals }) => {
  return { user: locals.user ?? null };
};
svelte
<!-- src/routes/+layout.svelte -->
<script lang="ts">
  import type { LayoutData } from './$types';
  export let data: LayoutData;
</script>

<nav>
  <a href="/">首页</a>
  <a href="/blog">博客</a>
  {#if data.user}
    <a href="/dashboard">控制台</a>
  {/if}
</nav>

<slot />  <!-- 子页面在此渲染 -->
typescript
// src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types';

export const load: LayoutServerLoad = async ({ locals }) => {
  return { user: locals.user ?? null };
};

Step 7: Rendering Modes

步骤7:渲染模式

Control per-route rendering with page options:
typescript
// src/routes/docs/+page.ts
export const prerender = true;   // Static — generated at build time
export const ssr = true;         // Default — rendered on server per request
export const csr = false;        // Disable client-side hydration entirely
通过页面选项控制每个路由的渲染方式:
typescript
// src/routes/docs/+page.ts
export const prerender = true;   // 静态——构建时生成
export const ssr = true;         // 默认——每次请求在服务器端渲染
export const csr = false;        // 完全禁用客户端水合

Examples

示例

Example 1: Protected Dashboard Route

示例1:受保护的控制台路由

typescript
// src/routes/dashboard/+layout.server.ts
import { redirect } from '@sveltejs/kit';
import type { LayoutServerLoad } from './$types';

export const load: LayoutServerLoad = async ({ locals }) => {
  if (!locals.user) {
    redirect(303, '/login');
  }
  return { user: locals.user };
};
typescript
// src/routes/dashboard/+layout.server.ts
import { redirect } from '@sveltejs/kit';
import type { LayoutServerLoad } from './$types';

export const load: LayoutServerLoad = async ({ locals }) => {
  if (!locals.user) {
    redirect(303, '/login');
  }
  return { user: locals.user };
};

Example 2: Hooks — Session Middleware

示例2:钩子——会话中间件

typescript
// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';
import { verifyToken } from '$lib/server/auth';

export const handle: Handle = async ({ event, resolve }) => {
  const token = event.cookies.get('session');
  if (token) {
    event.locals.user = await verifyToken(token);
  }
  return resolve(event);
};
typescript
// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';
import { verifyToken } from '$lib/server/auth';

export const handle: Handle = async ({ event, resolve }) => {
  const token = event.cookies.get('session');
  if (token) {
    event.locals.user = await verifyToken(token);
  }
  return resolve(event);
};

Example 3: Preloading and Invalidation

示例3:预加载与失效

svelte
<script lang="ts">
  import { invalidateAll } from '$app/navigation';

  async function refresh() {
    await invalidateAll(); // re-runs all load functions on the page
  }
</script>

<button on:click={refresh}>Refresh</button>
svelte
<script lang="ts">
  import { invalidateAll } from '$app/navigation';

  async function refresh() {
    await invalidateAll(); // 重新运行页面上的所有load函数
  }
</script>

<button on:click={refresh}>刷新</button>

Best Practices

最佳实践

  • ✅ Use
    +page.server.ts
    for database/auth logic — it never ships to the client
  • ✅ Use
    $lib/server/
    for shared server-only modules (DB client, auth helpers)
  • ✅ Use form actions for mutations instead of client-side
    fetch
    — works without JS
  • ✅ Type all
    load
    return values with generated
    $types
    (
    PageData
    ,
    LayoutData
    )
  • ✅ Use
    event.locals
    in hooks to pass server-side context to load functions
  • ❌ Don't import server-only code in
    +page.svelte
    or
    +layout.svelte
    directly
  • ❌ Don't store sensitive state in stores — use
    locals
    on the server
  • ❌ Don't skip
    use:enhance
    on forms — without it, forms lose progressive enhancement
  • ✅ 使用
    +page.server.ts
    处理数据库/认证逻辑——它永远不会发送到客户端
  • ✅ 使用
    $lib/server/
    存放共享的仅服务器端模块(数据库客户端、认证助手)
  • ✅ 使用表单操作处理突变,而非客户端
    fetch
    ——无JS环境下也能工作
  • ✅ 使用生成的
    $types
    PageData
    LayoutData
    )为所有
    load
    返回值添加类型
  • ✅ 在钩子中使用
    event.locals
    将服务器端上下文传递给load函数
  • ❌ 不要在
    +page.svelte
    +layout.svelte
    中直接导入仅服务器端代码
  • ❌ 不要在存储中存储敏感状态——在服务器端使用
    locals
  • ❌ 不要在表单上跳过
    use:enhance
    ——没有它,表单会失去渐进式增强能力

Security & Safety Notes

安全注意事项

  • All code in
    +page.server.ts
    ,
    +server.ts
    , and
    $lib/server/
    runs exclusively on the server — safe for DB queries, secrets, and session validation.
  • Always validate and sanitize form data before database writes.
  • Use
    error(403)
    or
    redirect(303)
    from
    @sveltejs/kit
    rather than returning raw error objects.
  • Set
    httpOnly: true
    and
    secure: true
    on all auth cookies.
  • CSRF protection is built-in for form actions — do not disable
    checkOrigin
    in production.
  • +page.server.ts
    +server.ts
    $lib/server/
    中的所有代码仅在服务器端运行——可安全用于数据库查询、密钥和会话验证。
  • 在写入数据库前,始终验证并清理表单数据。
  • 使用
    @sveltejs/kit
    中的
    error(403)
    redirect(303)
    ,而非返回原始错误对象。
  • 为所有认证Cookie设置
    httpOnly: true
    secure: true
  • 表单操作内置CSRF保护——生产环境中不要禁用
    checkOrigin

Common Pitfalls

常见陷阱

  • Problem:
    Cannot use import statement in a module
    in
    +page.server.ts
    Solution: The file must be
    .ts
    or
    .js
    , not
    .svelte
    . Server files and Svelte components are separate.
  • Problem: Store value is
    undefined
    on first SSR render Solution: Populate the store from the
    load
    function return value (
    data
    prop), not from client-side
    onMount
    .
  • Problem: Form action does not redirect after submit Solution: Use
    redirect(303, '/path')
    from
    @sveltejs/kit
    , not a plain
    return
    . 303 is required for POST redirects.
  • Problem:
    locals.user
    is undefined inside a
    +page.server.ts
    load function Solution: Set
    event.locals.user
    in
    src/hooks.server.ts
    before the
    resolve()
    call.
  • 问题:
    +page.server.ts
    中出现
    Cannot use import statement in a module
    错误 解决方案: 文件必须是
    .ts
    .js
    ,而非
    .svelte
    。服务器文件与Svelte组件是分离的。
  • 问题: 首次SSR渲染时存储值为
    undefined
    解决方案:
    load
    函数的返回值(
    data
    属性)填充存储,而非客户端
    onMount
  • 问题: 表单提交后未重定向 解决方案: 使用
    @sveltejs/kit
    中的
    redirect(303, '/path')
    ,而非普通
    return
    。POST重定向需要使用303状态码。
  • 问题:
    +page.server.ts
    的load函数中
    locals.user
    未定义 解决方案:
    src/hooks.server.ts
    resolve()
    调用前设置
    event.locals.user

Related Skills

相关技能

  • @nextjs-app-router-patterns
    — When you prefer React over Svelte for SSR/SSG
  • @trpc-fullstack
    — Add end-to-end type safety to SvelteKit API routes
  • @auth-implementation-patterns
    — Authentication patterns usable with SvelteKit hooks
  • @tailwind-patterns
    — Styling SvelteKit apps with Tailwind CSS
  • @nextjs-app-router-patterns
    —— 当你更喜欢用React而非Svelte进行SSR/SSG时
  • @trpc-fullstack
    —— 为SvelteKit API路由添加端到端类型安全
  • @auth-implementation-patterns
    —— 可用于SvelteKit钩子的认证模式
  • @tailwind-patterns
    —— 使用Tailwind CSS为SvelteKit应用设置样式

Limitations

局限性

  • Use this skill only when the task clearly matches the scope described above.
  • Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
  • Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.
  • 仅当任务明确符合上述描述的范围时才使用此技能。
  • 不要将输出视为特定环境验证、测试或专家评审的替代品。
  • 如果缺少所需输入、权限、安全边界或成功标准,请停止并请求澄清。