路由系统 #
一、路由概述 #
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 布局 -->
五、导航 #
5.1 Link 组件 #
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