路由系统 #

一、路由概述 #

SvelteKit 使用文件系统路由,目录结构直接映射为 URL 路径。

text
┌─────────────────────────────────────────────────────────────┐
│                    文件系统路由映射                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  文件路径                              URL                  │
│  ────────────────────────────────────────────────────────  │
│  routes/+page.svelte                  /                    │
│  routes/about/+page.svelte            /about               │
│  routes/blog/+page.svelte             /blog                │
│  routes/blog/[slug]/+page.svelte      /blog/:slug          │
│  routes/[...catchall]/+page.svelte    /*                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

二、基本路由 #

2.1 静态路由 #

text
src/routes/
├── +page.svelte          → /
├── about/
│   └── +page.svelte      → /about
├── contact/
│   └── +page.svelte      → /contact
└── blog/
    ├── +page.svelte      → /blog
    └── archive/
        └── +page.svelte  → /blog/archive

2.2 页面文件 #

文件 说明
+page.svelte 页面组件
+page.ts 页面数据加载(客户端+服务端)
+page.server.ts 服务端数据加载
+page.js .ts 但为 JavaScript

2.3 创建页面 #

src/routes/about/+page.svelte

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

<h1>About Us</h1>
<p>{data.description}</p>

src/routes/about/+page.ts

typescript
export async function load() {
  return {
    description: 'This is the about page.'
  };
}

三、动态路由 #

3.1 参数路由 #

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

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

typescript
export async function load({ params }) {
  const { slug } = params;
  
  const response = await fetch(`/api/posts/${slug}`);
  const post = await response.json();
  
  return { post };
}

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

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

<h1>{data.post.title}</h1>
<p>{data.post.content}</p>

3.2 多参数路由 #

text
src/routes/
└── users/
    └── [userId]/
        └── posts/
            └── [postId]/
                └── +page.svelte  → /users/:userId/posts/:postId
typescript
export async function load({ params }) {
  const { userId, postId } = params;
  
  return {
    userId,
    postId
  };
}

3.3 可选参数 #

text
src/routes/
└── [[lang]]/
    └── +page.svelte  → / 或 /en 或 /zh
typescript
export async function load({ params }) {
  const lang = params.lang || 'en';
  
  return { lang };
}

3.4 匹配器 #

src/params/id.ts

typescript
export function match(param: string) {
  return /^\d+$/.test(param);
}

src/routes/blog/[id=id]/+page.svelte

svelte
<!-- 只匹配数字 ID -->
<!-- /blog/123 ✓ -->
<!-- /blog/abc ✗ -->

四、布局路由 #

4.1 根布局 #

src/routes/+layout.svelte

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

<header>
  <nav>
    <a href="/">Home</a>
    <a href="/about">About</a>
    <a href="/blog">Blog</a>
  </nav>
</header>

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

<footer>
  <p>© 2024</p>
</footer>

4.2 嵌套布局 #

text
src/routes/
├── +layout.svelte          根布局
├── blog/
│   ├── +layout.svelte      博客布局
│   ├── +page.svelte
│   └── [slug]/
│       └── +page.svelte

src/routes/blog/+layout.svelte

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

<div class="blog-layout">
  <aside>
    <h3>Categories</h3>
    <ul>
      {#each data.categories as category}
        <li><a href="/blog?category={category}">{category}</a></li>
      {/each}
    </ul>
  </aside>
  
  <div class="content">
    {@render children()}
  </div>
</div>

4.3 布局数据 #

src/routes/blog/+layout.ts

typescript
export async function load() {
  return {
    categories: ['Tech', 'Life', 'Travel']
  };
}

4.4 重置布局 #

src/routes/(auth)/+layout.svelte

svelte
<!-- (auth) 分组不会影响 URL -->
<script>
  let { children } = $props();
</script>

<div class="auth-layout">
  {@render children()}
</div>

src/routes/(auth)/login/+page.svelte

svelte
<!-- URL: /login -->
<!-- 使用 auth 布局 -->

五、导航 #

svelte
<script>
  import { goto } from '$app/navigation';
</script>

<a href="/about">About</a>

<button onclick={() => goto('/about')}>
  Go to About
</button>

5.2 编程式导航 #

svelte
<script>
  import { goto, invalidate } from '$app/navigation';
  
  async function navigate() {
    await goto('/dashboard', {
      replaceState: true,
      keepFocus: true,
      noscroll: true
    });
  }
  
  function refresh() {
    invalidate('/api/data');
  }
</script>

<button onclick={navigate}>Navigate</button>
<button onclick={refresh}>Refresh Data</button>

5.3 页面状态 #

svelte
<script>
  import { page } from '$app/stores';
  
  $: searchParams = $page.url.searchParams;
  $: category = searchParams.get('category');
</script>

<p>Category: {category}</p>

<a href="?category=tech">Tech</a>
<a href="?category=life">Life</a>

5.4 导航事件 #

src/routes/+layout.svelte

svelte
<script>
  import { onNavigate, beforeNavigate } from '$app/navigation';
  
  onNavigate((navigation) => {
    console.log('Navigating to:', navigation.to?.url.pathname);
  });
  
  beforeNavigate((navigation) => {
    if (hasUnsavedChanges()) {
      return confirm('Discard changes?');
    }
  });
</script>

六、路由分组 #

6.1 分组语法 #

text
src/routes/
├── (marketing)/
│   ├── +layout.svelte
│   ├── +page.svelte          → /
│   ├── about/
│   │   └── +page.svelte      → /about
│   └── contact/
│       └── +page.svelte      → /contact
└── (app)/
    ├── +layout.svelte
    ├── dashboard/
    │   └── +page.svelte      → /dashboard
    └── settings/
        └── +page.svelte      → /settings

6.2 分组用途 #

text
分组用途
├── 共享布局而不影响 URL
├── 组织代码结构
├── 不同页面组使用不同布局
└── 条件渲染布局元素

七、错误处理 #

7.1 错误页面 #

src/routes/+error.svelte

svelte
<script>
  import { page } from '$app/stores';
</script>

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

{#if $page.status === 404}
  <p>The page you're looking for doesn't exist.</p>
{:else if $page.status === 500}
  <p>Something went wrong on our end.</p>
{/if}

<a href="/">Go Home</a>

7.2 嵌套错误页面 #

src/routes/blog/+error.svelte

svelte
<script>
  import { page } from '$app/stores';
</script>

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

<a href="/blog">Back to Blog</a>

7.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',
      hint: 'Try a different slug'
    });
  }
  
  return { post };
}

八、重定向 #

8.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 };
}

8.2 客户端重定向 #

svelte
<script>
  import { goto } from '$app/navigation';
  import { onMount } from 'svelte';
  
  onMount(() => {
    goto('/dashboard');
  });
</script>

<p>Redirecting...</p>

8.3 永久重定向 #

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

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

九、路由守卫 #

9.1 认证守卫 #

src/routes/(app)/+layout.server.ts

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

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

9.2 权限检查 #

src/routes/admin/+page.server.ts

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

export async function load({ locals }) {
  if (locals.user?.role !== 'admin') {
    throw error(403, 'Forbidden');
  }
  
  return {};
}

十、完整示例 #

10.1 博客路由结构 #

text
src/routes/
├── +layout.svelte           全局布局
├── +layout.ts               全局数据
├── +error.svelte            全局错误页
├── +page.svelte             首页
├── blog/
│   ├── +layout.svelte       博客布局
│   ├── +layout.ts           博客数据
│   ├── +page.svelte         博客列表
│   ├── +page.ts             列表数据
│   ├── +error.svelte        博客错误页
│   ├── [slug]/
│   │   ├── +page.svelte     文章详情
│   │   └── +page.ts         文章数据
│   └── category/
│       └── [name]/
│           ├── +page.svelte 分类文章
│           └── +page.ts     分类数据
└── (auth)/
    ├── +layout.svelte       认证布局
    ├── login/
    │   └── +page.svelte     登录页
    └── register/
        └── +page.svelte     注册页

十一、总结 #

路由类型 语法 示例
静态路由 about/+page.svelte /about
动态路由 [slug]/+page.svelte /blog/hello
可选参数 [[lang]]/+page.svelte //en
匹配器 [id=id]/+page.svelte /blog/123
分组 (group)/+page.svelte 不影响 URL
捕获所有 [...path]/+page.svelte /a/b/c

路由要点:

  • 文件系统自动映射路由
  • [param] 定义动态参数
  • +layout.svelte 定义布局
  • +error.svelte 处理错误
  • (group) 分组不影响 URL
  • 使用 goto 编程式导航
最后更新:2026-03-28