数据加载 #

一、数据加载概述 #

SvelteKit 使用 load 函数在页面渲染前获取数据。

text
┌─────────────────────────────────────────────────────────────┐
│                    数据加载流程                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  用户访问 URL                                                │
│       ↓                                                     │
│  路由匹配                                                    │
│       ↓                                                     │
│  执行 load 函数                                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  +layout.ts (从根到叶)                              │   │
│  │  +layout.server.ts                                  │   │
│  │  +page.ts                                           │   │
│  │  +page.server.ts                                    │   │
│  └─────────────────────────────────────────────────────┘   │
│       ↓                                                     │
│  数据合并传递给页面                                          │
│       ↓                                                     │
│  渲染页面                                                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

二、load 函数 #

2.1 基本用法 #

src/routes/+page.ts

typescript
export async function load() {
  return {
    message: 'Hello from load function'
  };
}

src/routes/+page.svelte

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

<p>{data.message}</p>

2.2 load 参数 #

typescript
export async function load({ 
  params,      // URL 参数
  url,         // URL 对象
  fetch,       // fetch 函数
  cookies,     // cookies 对象
  request,     // Request 对象
  route,       // 路由信息
  parent,      // 父布局数据
  depends,     // 依赖声明
  setHeaders,  // 设置响应头
  untrack      // 取消追踪
}) {
  return {};
}

2.3 获取 URL 参数 #

typescript
export async function load({ params, url }) {
  const { slug } = params;
  const category = url.searchParams.get('category');
  const page = url.searchParams.get('page') || '1';
  
  return {
    slug,
    category,
    page: parseInt(page)
  };
}

三、服务端数据加载 #

3.1 +page.server.ts #

typescript
export async function load({ fetch, cookies }) {
  const token = cookies.get('token');
  
  const response = await fetch('/api/user', {
    headers: {
      Authorization: `Bearer ${token}`
    }
  });
  
  const user = await response.json();
  
  return { user };
}

3.2 访问私有资源 #

typescript
import { DATABASE } from '$lib/server/database';

export async function load({ params }) {
  const post = await DATABASE.posts.findUnique({
    where: { slug: params.slug }
  });
  
  return { post };
}

3.3 Cookies 操作 #

typescript
export async function load({ cookies }) {
  const sessionId = cookies.get('session_id');
  
  if (sessionId) {
    const session = await getSession(sessionId);
    return { session };
  }
  
  return { session: null };
}

四、布局数据 #

4.1 根布局数据 #

src/routes/+layout.ts

typescript
export async function load() {
  return {
    siteName: 'My Blog',
    navigation: [
      { title: 'Home', href: '/' },
      { title: 'Blog', href: '/blog' },
      { title: 'About', href: '/about' }
    ]
  };
}

src/routes/+layout.svelte

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

<header>
  <h1>{data.siteName}</h1>
  <nav>
    {#each data.navigation as item}
      <a href={item.href}>{item.title}</a>
    {/each}
  </nav>
</header>

<main>
  {@render children()}
</main>

4.2 访问父布局数据 #

src/routes/blog/+page.ts

typescript
export async function load({ parent }) {
  const parentData = await parent();
  
  console.log(parentData.siteName);
  
  return {
    posts: await getPosts()
  };
}

4.3 数据合并 #

text
+layout.ts        → { siteName: 'My Blog' }
+layout.server.ts → { user: { name: 'Alice' } }
+page.ts          → { posts: [...] }
+page.server.ts   → { settings: {...} }

最终 data = {
  siteName: 'My Blog',
  user: { name: 'Alice' },
  posts: [...],
  settings: {...}
}

五、数据依赖 #

5.1 depends 函数 #

typescript
export async function load({ fetch, depends }) {
  depends('data:posts');
  
  const response = await fetch('/api/posts');
  const posts = await response.json();
  
  return { posts };
}

5.2 手动刷新 #

svelte
<script>
  import { invalidate } from '$app/navigation';
  
  let { data } = $props();
  
  function refresh() {
    invalidate('data:posts');
  }
</script>

<button onclick={refresh}>Refresh</button>

{#each data.posts as post}
  <p>{post.title}</p>
{/each}

5.3 URL 依赖 #

typescript
export async function load({ url, depends }) {
  depends(url.pathname);
  
  const response = await fetch(`/api/page${url.pathname}`);
  const data = await response.json();
  
  return { data };
}

六、错误处理 #

6.1 抛出错误 #

typescript
import { error } from '@sveltejs/kit';

export async function load({ params }) {
  const post = await getPost(params.slug);
  
  if (!post) {
    throw error(404, 'Post not found');
  }
  
  return { post };
}

6.2 错误类型 #

typescript
import { error } from '@sveltejs/kit';

export async function load() {
  // 400 Bad Request
  throw error(400, 'Invalid request');
  
  // 401 Unauthorized
  throw error(401, 'Not authenticated');
  
  // 403 Forbidden
  throw error(403, 'Access denied');
  
  // 404 Not Found
  throw error(404, 'Resource not found');
  
  // 500 Internal Server Error
  throw error(500, 'Server error');
}

6.3 自定义错误数据 #

typescript
import { error } from '@sveltejs/kit';

export async function load({ params }) {
  const post = await getPost(params.slug);
  
  if (!post) {
    throw error(404, {
      message: 'Post not found',
      suggestion: 'Check the URL or browse our posts'
    });
  }
  
  return { post };
}

七、重定向 #

7.1 基本重定向 #

typescript
import { redirect } from '@sveltejs/kit';

export async function load({ cookies }) {
  const session = cookies.get('session');
  
  if (!session) {
    throw redirect(302, '/login');
  }
  
  return { session };
}

7.2 条件重定向 #

typescript
import { redirect } from '@sveltejs/kit';

export async function load({ url, cookies }) {
  const session = cookies.get('session');
  
  if (!session && url.pathname !== '/login') {
    throw redirect(302, `/login?redirect=${url.pathname}`);
  }
  
  return { session };
}

7.3 永久重定向 #

typescript
import { redirect } from '@sveltejs/kit';

export async function load() {
  throw redirect(301, '/new-location');
}

八、并行加载 #

8.1 Promise.all #

typescript
export async function load({ fetch }) {
  const [users, posts, comments] = await Promise.all([
    fetch('/api/users').then(r => r.json()),
    fetch('/api/posts').then(r => r.json()),
    fetch('/api/comments').then(r => r.json())
  ]);
  
  return { users, posts, comments };
}

8.2 条件加载 #

typescript
export async function load({ url, fetch }) {
  const category = url.searchParams.get('category');
  const page = url.searchParams.get('page') || '1';
  
  const params = new URLSearchParams({ page });
  if (category) params.append('category', category);
  
  const response = await fetch(`/api/posts?${params}`);
  const posts = await response.json();
  
  return { posts, category, page: parseInt(page) };
}

九、完整示例 #

9.1 博客文章页面 #

src/routes/blog/[slug]/+page.server.ts

typescript
import { error } from '@sveltejs/kit';
import { db } from '$lib/server/database';

export async function load({ params }) {
  const post = await db.posts.findUnique({
    where: { slug: params.slug },
    include: {
      author: true,
      comments: {
        include: { author: true },
        orderBy: { createdAt: 'desc' }
      }
    }
  });
  
  if (!post) {
    throw error(404, 'Post not found');
  }
  
  return { post };
}

src/routes/blog/[slug]/+page.svelte

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

<article>
  <header>
    <h1>{data.post.title}</h1>
    <p>
      By {data.post.author.name} • 
      {new Date(data.post.createdAt).toLocaleDateString()}
    </p>
  </header>
  
  <div class="content">
    {data.post.content}
  </div>
  
  <section class="comments">
    <h2>Comments ({data.post.comments.length})</h2>
    
    {#each data.post.comments as comment}
      <div class="comment">
        <strong>{comment.author.name}</strong>
        <p>{comment.content}</p>
      </div>
    {/each}
  </section>
</article>

9.2 分页数据 #

src/routes/blog/+page.ts

typescript
export async function load({ url, fetch }) {
  const page = parseInt(url.searchParams.get('page') || '1');
  const limit = 10;
  
  const response = await fetch(`/api/posts?page=${page}&limit=${limit}`);
  const { posts, total } = await response.json();
  
  return {
    posts,
    pagination: {
      page,
      limit,
      total,
      totalPages: Math.ceil(total / limit)
    }
  };
}

src/routes/blog/+page.svelte

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

<h1>Blog Posts</h1>

{#each data.posts as post}
  <article>
    <h2><a href="/blog/{post.slug}">{post.title}</a></h2>
    <p>{post.excerpt}</p>
  </article>
{/each}

<div class="pagination">
  {#if data.pagination.page > 1}
    <a href="?page={data.pagination.page - 1}">Previous</a>
  {/if}
  
  <span>Page {data.pagination.page} of {data.pagination.totalPages}</span>
  
  {#if data.pagination.page < data.pagination.totalPages}
    <a href="?page={data.pagination.page + 1}">Next</a>
  {/if}
</div>

十、总结 #

文件 执行环境 用途
+page.ts 客户端+服务端 公共数据加载
+page.server.ts 仅服务端 私有数据、数据库访问
+layout.ts 客户端+服务端 布局数据
+layout.server.ts 仅服务端 布局私有数据

数据加载要点:

  • load 函数在渲染前执行
  • +page.server.ts 仅在服务端运行
  • 使用 parent() 访问父布局数据
  • 使用 depends() 声明数据依赖
  • 使用 error() 抛出错误
  • 使用 redirect() 重定向
最后更新:2026-03-28