F FisherHub Docs

02. 路由与数据加载

SvelteKit 的文件路由系统不仅支持静态路径,还提供了动态参数、布局嵌套和服务端数据加载能力。本章将逐一展开。

动态路由参数

当路由路径不确定时,使用方括号 [slug] 声明动态参数。

src/routes/
└── blog/
    ├── [slug]/
    │   └── +page.svelte  → /blog/:slug
    └── +page.svelte      → /blog

在页面组件中通过 $page.params 获取参数:

<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
  import { page } from '$app/stores';

  let slug = $derived($page.params.slug);
</script>

<h1>文章: {slug}</h1>

多段参数

使用 [...path] 语法匹配多级路径:

src/routes/
└── docs/
    └── [...path]/
        └── +page.svelte  → /docs/a/b/c
<script lang="ts">
  import { page } from '$app/stores';

  let path = $derived($page.params.path);
</script>

<p>文档路径: {path}</p>
<!-- 访问 /docs/guide/getting-started → "guide/getting-started" -->

可选参数

使用 [[param]] 双括号语法让参数可选:

src/routes/
└── [[lang]]/
    └── +page.svelte  → / 或 /en 或 /zh-CN

布局组件 (+layout.svelte)

布局组件包裹其目录下的所有页面。你可以在布局中定义公共的页头、页脚和侧边栏。

<!-- src/routes/blog/+layout.svelte -->
<script lang="ts">
  let { children } = $props();
</script>

<nav>
  <a href="/blog">博客首页</a>
  <a href="/blog/archive">归档</a>
</nav>

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

<footer>© 2026 FisherHub</footer>

布局可以嵌套。根级 src/routes/+layout.svelte 包裹整个应用,子目录的布局只作用于对应路由子树。

布局数据 +layout.ts

布局可以拥有自己的 load 函数,共享数据给所有子页面:

// src/routes/blog/+layout.ts
import type { LayoutLoad } from './$types';

export const load: LayoutLoad = async () => {
  return {
    categories: ['技术', '生活', '开源'],
    siteName: 'FisherHub 博客'
  };
};

在布局或子页面中通过 data 属性接收:

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

<h1>{data.siteName}</h1>

数据加载 (+page.ts load)

+page.tsload 函数在服务端执行(SSR 时),返回的数据会注入到页面的 data 属性。

// src/routes/blog/[slug]/+page.ts
import type { PageLoad } from './$types';

export const load: PageLoad = async ({ params, fetch }) => {
  const slug = params.slug;

  // 注意:这里的 fetch 是 SvelteKit 的增强版 fetch
  // 在服务端运行时,它会直接调用内部 API,避免额外的 HTTP 请求
  const res = await fetch(`/api/posts/${slug}.json`);

  if (!res.ok) {
    throw error(404, '文章不存在');
  }

  const post = await res.json();

  return {
    post
  };
};

通用 load 函数

+page.ts 中的 load 既在服务端运行(首次访问),也在客户端运行(后续导航)。如果你只需要在服务端获取数据,使用 +page.server.ts

// src/routes/blog/[slug]/+page.server.ts
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ params }) => {
  // 这里可以直接访问数据库、API key 等
  const post = await getPostFromDatabase(params.slug);

  if (!post) {
    throw error(404, '文章不存在');
  }

  return { post };
};

关键区别

文件运行位置使用场景
+page.ts服务端 + 客户端调用公共 API、通用数据获取
+page.server.ts仅服务端访问数据库、私密凭证、敏感操作

错误页面 (+error.svelte)

load 函数或页面渲染抛出错误时,SvelteKit 会渲染同级的 +error.svelte

<!-- src/routes/+error.svelte -->
<script lang="ts">
  import { page } from '$app/stores';
  import { error } from '@sveltejs/kit';
</script>

<h1>{$page.status}</h1>
<p>{$page.error?.message}</p>

<a href="/">返回首页</a>

你可以为不同的路由层级添加专属错误页面:

src/routes/
├── +error.svelte          ← 全局 404/500
└── blog/
    ├── +error.svelte      ← /blog 下的专属错误页
    └── [slug]/
        └── +error.svelte  ← 单篇文章的错误页

数据预取

在链接上使用 data-sveltekit-preload-datadata-sveltekit-preload-code 提前加载数据:

<a href="/blog/my-post" data-sveltekit-preload-data>预取数据</a>

也可以在根布局中全局开启:

<!-- src/routes/+layout.svelte -->
<script lang="ts">
  import { configure } from '$app/stores';
</script>

<div data-sveltekit-preload-data="hover">
  {@render children()}
</div>

设置为 hover 后,用户鼠标悬停在链接上时就开始加载数据,导航几乎瞬间完成。

总结

本章介绍了:

  • 动态路由参数 [slug][...path][[param]] 的用法
  • +layout.svelte 实现布局嵌套和数据共享
  • +page.ts+page.server.ts 的数据加载机制
  • +error.svelte 创建友好的错误页面
  • 通过 data-sveltekit-preload-data 实现预取优化

下一章将学习表单处理和服务端操作。