load 函数体系
两种 load 函数
SvelteKit 提供两种数据加载方式,通过不同文件承载:
+page.server.ts
服务端专用 load 函数。只在服务器运行,可安全访问数据库、文件系统、私有 API,不暴露给客户端。适合需要认证或敏感数据的场景。
+page.ts
通用 load 函数。在服务端 SSR 时运行,在客户端导航时也在浏览器运行。适合公开 API 调用,代码在两端共享。
+layout.server.ts
布局级服务端 load,返回的数据对该布局下所有页面可用。常用于加载用户认证信息。
PageData 类型
SvelteKit 自动生成的类型,表示 load 函数返回值的类型。通过 import type { PageData } from './$types' 引入,保证页面组件的类型安全。
+page.server.ts 服务端 load
// src/routes/blog/[slug]/+page.server.ts
import type { PageServerLoad } from './$types';
import { error } from '@sveltejs/kit';
import { db } from '$lib/server/db'; // 服务端专用模块
export const load: PageServerLoad = async ({
params, // 路由参数
locals, // 中间件传递的数据(如当前用户)
cookies, // Cookie 访问
request, // 原始 Request 对象
url, // URL 对象
fetch, // 增强版 fetch,自动携带 cookies
setHeaders // 设置响应头
}) => {
// 认证检查
if (!locals.user) {
error(401, '请先登录');
}
// 数据库查询(服务端专用)
const post = await db.post.findUnique({
where: { slug: params.slug }
});
if (!post) error(404, '文章不存在');
// 设置缓存头
setHeaders({ 'cache-control': 'max-age=3600' });
// 返回值自动成为 PageData 的一部分
return {
post,
author: locals.user
};
};
+page.ts 通用 load
// src/routes/products/+page.ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ fetch, url }) => {
const category = url.searchParams.get('category') || 'all';
// 这个 fetch 在服务端和客户端都能正常工作
const res = await fetch(`/api/products?category=${category}`);
const products = await res.json();
return { products, category };
};
// 禁用 SSR(纯客户端渲染)
export const ssr = false;
// 禁用预加载
export const prerender = false;
在页面中使用 data
<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
import type { PageData } from './$types';
let { data } = $props<{ data: PageData }>();
// data.post 和 data.author 已有完整类型信息
</script>
<svelte:head>
<title>{data.post.title}</title>
</svelte:head>
<article>
<h1>{data.post.title}</h1>
<p class="meta">作者:{data.author.name}</p>
<div class="content">{@html data.post.htmlContent}</div>
</article>
缓存刷新
<script lang="ts">
import { invalidate, invalidateAll } from '$app/navigation';
async function refreshData() {
// 使特定 URL 的数据失效,触发重新加载
await invalidate('app:user'); // 自定义 key
await invalidate('/api/posts'); // URL key
// 使所有数据失效(重新运行所有 load 函数)
await invalidateAll();
}
</script>
流式加载
// 流式加载:先快速返回重要数据,慢查询用 Promise 流式传入
export const load: PageServerLoad = async ({ params }) => {
// 快速数据:立即返回
const post = await db.post.findUnique({ where: { id: params.id } });
// 慢速数据:不 await,作为 Promise 返回
const commentsPromise = db.comment.findMany({
where: { postId: params.id }
});
return {
post, // 立即可用
comments: commentsPromise // 流式加载
};
};
<!-- 在模板中处理流式数据 -->
<h1>{data.post.title}</h1>
{#await data.comments}
<p>加载评论...</p>
{:then comments}
{#each comments as comment}
<div class="comment">{comment.content}</div>
{/each}
{/await}
流式加载的 SEO 影响
流式加载时,页面 HTML 会分批发送给浏览器。对于 SEO 关键内容(标题、摘要),确保在同步数据中返回;评论、推荐等次要内容适合流式加载,既不影响 SEO 也改善了首次内容渲染(FCP)时间。
SSR/CSR/SSG 的深层原理
三种渲染模式的本质区别
理解这三种模式的工作原理,才能在每种场景下做出正确选择:
服务端渲染(SSR — Server-Side Rendering)
每次用户请求时,服务器实时执行 load 函数获取数据,生成完整的 HTML 后返回给浏览器。优点:首屏内容立即可见(对 SEO 友好)、数据始终最新(不存在缓存过期问题)。缺点:每次请求都有服务器计算开销。SvelteKit 默认开启 SSR。
关键配置:
关键配置:
export const ssr = true;(默认)客户端渲染(CSR — Client-Side Rendering)
服务器只返回一个空的 HTML 壳,JavaScript 在浏览器中运行,获取数据并渲染内容。优点:服务器负担轻(静态文件 CDN 托管)、交互性强。缺点:首屏白屏时间长(等 JS 加载执行)、SEO 差(爬虫看不到内容)。
关键配置:
关键配置:
export const ssr = false;静态站点生成(SSG — Static Site Generation)
构建时一次性执行所有 load 函数,生成纯静态 HTML/CSS/JS 文件。优点:极快的加载速度(CDN 全球分发)、无服务器成本。缺点:数据更新需要重新构建,不适合需要实时数据的页面。
关键配置:
关键配置:
export const prerender = true;混合渲染(Hybrid Rendering)
SvelteKit 支持在同一应用中混合使用三种模式:首页和博客文章使用 SSG(稳定内容),用户仪表板使用 SSR(实时数据),评论区使用 CSR(不需要 SEO)。每个路由独立配置,这是 SvelteKit 最强大的特性之一。
数据加载的执行时序
SSR 请求处理时序:
浏览器 SvelteKit 服务器 数据库
| | |
|--- GET /blog/my-post → | |
| |--- load() 执行 -----------→|
| |←-- post 数据 -------------|
| | [将 data 注入模板] |
|←-- 完整 HTML ----------| |
| | |
| [浏览器渲染 HTML] | |
| [加载 JS bundle] | |
| [Hydration:事件绑定] | |
Hydration(水合):
服务器生成的静态 HTML 被 Svelte 的 JS 接管,
添加事件监听器,变为可交互的应用。
如果服务器端和客户端渲染结果不一致,
会出现"hydration mismatch"警告。
load 函数的缓存与依赖追踪
// SvelteKit 会根据依赖自动决定是否重新执行 load
export const load: PageServerLoad = async ({ params, depends }) => {
// 声明这个 load 函数依赖 'app:user' 标识符
// 当 invalidate('app:user') 被调用时,自动重新执行此 load
depends('app:user');
const user = await getCurrentUser();
return { user };
};
// 在组件中触发重新加载
import { invalidate, invalidateAll } from '$app/navigation';
async function handleAvatarUpload() {
await uploadAvatar();
// 使所有依赖 'app:user' 的 load 函数失效,触发重新获取
await invalidate('app:user');
// 或者使整个页面的所有数据失效:
// await invalidateAll();
}
常见误区:load 函数中使用响应式语句
load 函数是普通的 async 函数,不是 Svelte 组件,无法使用 $state、$derived 等 Svelte 5 响应式原语。数据变更的响应式更新应通过 invalidate() 触发重新执行 load 函数来实现,而不是在 load 内部使用响应式变量。
错误处理与 +error.svelte
// +page.server.ts:使用 error() 返回 HTTP 错误
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params, locals }) => {
const post = await db.post.findUnique({ where: { slug: params.slug } });
if (!post) {
// 404 错误:SvelteKit 渲染最近的 +error.svelte
error(404, { message: `文章 "${params.slug}" 不存在` });
}
if (!post.published && !locals.user?.isAdmin) {
error(403, { message: '无权访问未发布的文章' });
}
return { post };
};
<!-- src/routes/+error.svelte(全局错误页面)-->
<script lang="ts">
import { page } from '$app/stores';
</script>
<svelte:head>
<title>{$page.status} — 出错了</title>
</svelte:head>
<div class="error-page">
<h1>{$page.status}</h1>
<p>{$page.error?.message || '发生了未知错误'}</p>
{#if $page.status === 404}
<a href="/">返回首页</a>
{:else if $page.status === 403}
<a href="/login">前往登录</a>
{:else}
<button onclick={() => location.reload()}>刷新页面</button>
{/if}
</div>
Layout load 与数据继承
layout.server.ts 的 load 函数返回的数据会自动合并到所有子页面的 data prop 中。这是在全应用范围传递数据(如用户信息、主题)的标准方式:
// src/routes/+layout.server.ts(根 layout)
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ locals, cookies }) => {
// locals 由 handle 钩子填充(通常是解析后的用户 session)
return {
user: locals.user, // 所有子页面通过 data.user 访问
theme: cookies.get('theme') ?? 'light'
};
};
<!-- 子页面 +page.svelte 访问 layout 数据 -->
<script lang="ts">
import type { PageData } from './$types';
let { data } = $props<{ data: PageData }>();
// data 自动合并:本页 load + 所有上级 layout load 的返回值
const { user, theme, post } = data; // user/theme 来自 layout, post 来自本页
</script>
<h1>欢迎,{user?.name ?? '访客'}</h1>
<article>{post.title}</article>
本章小结
- SSR/CSR/SSG 各有适用场景:动态内容+SEO 用 SSR,内部工具用 CSR,博客/文档用 SSG;SvelteKit 支持在同一应用混合使用。
- +page.server.ts 的 load 函数:只在服务端执行,可以安全访问数据库;返回的数据通过
dataprop 流向组件;layout.server.ts 的数据自动合并到子页面的 data 中。 - 流式加载(返回 Promise 而不是 await):快速数据立即返回,慢查询异步流入;配合
{#await}显示加载状态,提升用户体验。 - Hydration 是 SSR 后的关键步骤:服务端静态 HTML + 客户端 JS 事件绑定结合,确保服务端和客户端渲染结果一致;不一致时出现 hydration mismatch 警告。
- depends() + invalidate():声明 load 函数的依赖,数据变更时精确触发重新加载,比 invalidateAll 更高效。
- error() + +error.svelte:在 load 函数中抛出 HTTP 错误(404/403/500),SvelteKit 自动渲染最近的错误页面;根级 +error.svelte 是全局兜底。