Skip to main content

sveltekit-data

SvelteKit 数据加载、表单、页面选项技能。当用户在 SvelteKit 中使用 +page.js/+page.server.js 加载数据、使用 +server.js API 路由、处理表单 actions(默认/具名/GET)、使用 use:enhance 进行渐进增强、配置 prerender/ssr/csr/trailingSlash 页面选项时使用。

Jump to install

Source facts

Repository
full-stack-skills/svelte-skills
Last source activity
September 11, 2026 at 13:43
Detected SKILL.md language
Chinese
Stars
3
Forks
2

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
10 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
sveltekit-data
license
Apache-2.0
description
SvelteKit 数据加载、表单、页面选项技能。当用户在 SvelteKit 中使用 +page.js/+page.server.js 加载数据、使用 +server.js API 路由、处理表单 actions(默认/具名/GET)、使用 use:enhance 进行渐进增强、配置 prerender/ssr/csr/trailingSlash 页面选项时使用。
# SvelteKit Data: Loading, Form Actions, Page Options (SvelteKit 2) 本技能覆盖 SvelteKit 数据层的三大支柱:(1) Loading data(`+page.js` / `+page.server.js` / `+layout.js` / `+layout.server.js` 的 `load` 函数、`page.data`、URL 数据、cookies/headers、错误与重定向、流式响应、依赖追踪与手动失效),(2) Form actions(默认/具名 actions、`fail` 验证、`use:enhance` 渐进增强、`GET` vs `POST`),(3) Page options(`prerender` / `entries` / `ssr` / `csr` / `trailingSlash` / `config`)。 ## When to use this skill - 决定 `+page.js` vs `+page.server.js` 的取舍(隐私凭据、序列化、组合使用) - 在 `load` 函数中使用 `params` / `route` / `url` / `fetch` / `parent` / `depends` / `untrack` - 用 `page.data` 在父布局访问子页面数据 - 用 `error(status, msg)` / `redirect(status, location)` 终止 load/action - 用返回 Promise 实现 streaming 骨架屏 - 解决"load 函数何时重新执行"以及如何用 `invalidate` / `invalidateAll` / `untrack` 精确控制 - 在 server `load` / action 中读 `cookies` / 设响应头 - 设计 `+page.server.js` 的 actions:默认 action、具名 action、`?/name` 查询参数、`fail()` 验证 - 用 `use:enhance` 渐进增强表单、回填 `form` 字段、阻止默认重置、自定义 `applyAction` - 决定哪些路由 `prerender = true` / `entries()` / `ssr = false` / `csr = false` / `trailingSlash` ## Critical: Loading data (universal vs server load functions) SvelteKit 有两种 `load` 函数,运行位置和约束不同。 **Universal load**(`+page.js` / `+layout.js`):默认 SSR 时在服务端跑一次、客户端 hydration 时再跑一次;之后所有导航都在浏览器内运行。可以返回**任意 JS 值**(包括 Svelte 组件构造函数等不可序列化的对象)。`fetch` 在 SSR 阶段内联到 HTML,hydration 时复用,不会泄漏私密凭据到客户端。 **Server load**(`+page.server.js` / `+layout.server.js`):**永远在服务端跑**。返回值必须用 [devalue](https://github.com/rich-harris/devalue) 序列化(JSON + `BigInt` / `Date` / `Map` / `Set` / `RegExp` / 循环引用)。可访问 `cookies` / `locals` / `request` / `clientAddress` / `platform`。 ```js // +page.server.js import * as db from '$lib/server/database'; /** @type {import('./$types').PageServerLoad} */ export async function load({ params }) { return { post: await db.getPost(params.slug) }; } ``` ```js // +page.js —— 公共 API fetch,浏览器可直接拉 /** @type {import('./$types').PageLoad} */ export async function load({ fetch, params }) { const res = await fetch(`/api/items/${params.id}`); return { item: await res.json() }; } ``` **两者可以同时存在**。当同时存在时,server load 先跑,其返回值成为 universal load 的 `data` 参数;universal load 的返回值才到达页面: ```js // +page.server.js —— 仅在服务端,返回 sessionId export async function load({ locals }) { return { sessionId: locals.sessionId }; } // +page.js —— 浏览器也能跑,但能拿到服务端上下文 export async function load({ data, fetch }) { const res = await fetch(`/api/me`, { headers: { 'x-session': data.sessionId } }); return { me: await res.json() }; } ``` **何时用哪个**: - 需要私密环境变量、数据库、文件系统 → server load - 数据是公开 API、且希望减少服务器往返 → universal load - 需要返回不可序列化对象(Svelte 组件 class) → universal load - 需要 streaming(慢数据 + 骨架屏) → **必须 server load**(universal load 的 Promise 不会被流式传输) - 需要在客户端也能重新跑 → universal load ## Critical: page.data (sharing data across components) 页面和所有祖先 layout 各有**自己的** `data` prop,包含它自己 + 全部祖先的合并数据。当**父**组件需要**子**组件返回的数据时,用 `$app/state` 的 `page.data`(`$app/stores` 的 `$page` 是旧式等价物): ```svelte <!-- src/routes/+layout.svelte --> <script> import { page } from '$app/state'; /** @type {import('./$types').LayoutProps} */ let { data, children } = $props(); </script> <svelte:head> <title>{page.data.title}</title> </svelte:head> {@render children()} ``` 合并规则:同级出现相同 key 时**后者覆盖**。`+layout.js` 返回 `{ a:1, b:2 }` + `+page.js` 返回 `{ b:3, c:4 }` → `data = { a:1, b:3, c:4 }`。 ## Critical: URL data (params/route/url) `load` 函数通过 `url` / `route` / `params` 访问 URL。`url.hash` 在 SSR 阶段不可用(服务端无 fragment)。 ```js // src/routes/a/[b]/[...c]/+page.js /** @type {import('./$types').PageLoad} */ export function load({ url, route, params }) { // route.id = '/a/[b]/[...c]' // url = URL 实例(origin/pathname/searchParams/...) // params.b = 'x' params.c = 'y/z' return { q: url.searchParams.get('q') }; } ``` `url.searchParams.get/getAll/has` 在依赖追踪中是**独立**的 key——`?x=1&y=1` → `?x=1&y=2` **不会**重跑只依赖 `y` 的 load。 ## Critical: Cookies and Headers **只有 server load** 可以读 / 写 `cookies`。`setHeaders` 在 universal load 中调用 SSR 时生效,浏览器内调用是 no-op。 ```js // +layout.server.js export async function load({ cookies }) { const sessionid = cookies.get('sessionid'); return { user: await db.getUser(sessionid) }; } // +page.js —— 转发上游 cache 头 export async function load({ fetch, setHeaders }) { const response = await fetch('https://cms.example.com/products.json'); setHeaders({ 'cache-control': response.headers.get('cache-control') }); return response.json(); } ``` 约束:`setHeaders` 同名 header 只能设一次;不能用 `setHeaders` 设 `set-cookie`(用 `cookies.set`);`fetch` 只在**同源或子域**目标带 cookies,其他域用 `handleFetch` hook。 ## Critical: Errors and Redirects `error(status, message)` 和 `redirect(status, location)` 都**直接抛异常**——不要自己 `throw`(SvelteKit 1.x 行为已废弃)。`redirect` **不要放在 try 块里**(会被 catch 吃掉)。 ```js import { error, redirect } from '@sveltejs/kit'; export function load({ locals }) { if (!locals.user) error(401, 'not logged in'); if (!locals.user.isAdmin) error(403, 'not an admin'); } export function load({ locals, url }) { if (!locals.user) { const next = url.pathname + url.search; redirect(303, `/login?redirectTo=${encodeURIComponent(next)}`); } } ``` `expected error`(用 `error()` 抛出)显示最近 `+error.svelte` 并带正确 status;`unexpected error` 触发 `handleError` hook,按 500 处理。 浏览器端导航用 `$app/navigation` 的 `goto`: ```js import { goto } from '$app/navigation'; goto('/login'); ``` ## Critical: Streaming with promises Server `load` 返回**未 await 的 Promise** 会被流式传输到浏览器,允许快数据先渲染、慢数据后到。 ```js // +page.server.js —— 关键数据先 await,慢数据后流 export async function load({ params }) { return { post: await loadPost(params.slug), comments: loadComments(params.slug) // 不 await → 单独流 }; } ``` ```svelte <!-- +page.svelte --> <h1>{data.post.title}</h1> {#await data.comments} <p>Loading comments...</p> {:then comments} {#each comments as c}<p>{c.content}</p>{/each} {:catch error} <p>error: {error.message}</p> {/await} ``` **强约束**: - streaming 仅在 JS 启用且非 Lambda/Firebase 等缓冲平台时生效 - 响应开始流式输出后**不能**再 `setHeaders` / `redirect` - 手写 Promise 加 `.catch(() => {})` 防止 unhandled rejection;`fetch` 由 SvelteKit 自动处理 - 嵌套 promise 内的 `params.x` 不会被依赖追踪——必须在顶层 body 访问 ## Critical: When load functions rerun SvelteKit 跟踪每个 load 的依赖以避免重跑。`load` 重新执行的条件: 1. 访问 `params` 某属性且值变了 2. 访问 `url.pathname` / `url.search` 等且值变了 3. `url.searchParams.get/getAll/has` 对应参数变了 4. `await parent()` 且父 load 重跑了 5. 通过 `fetch(url)` 或 `depends(url)` 声明依赖,且 `invalidate(url)` 被调用 6. `invalidateAll()` 强制重跑所有 active load ```js // +page.js —— 自定义依赖标签(约定 [a-z]: 前缀) export async function load({ fetch, depends }) { depends('app:random'); const r = await fetch('https://api.example.com/random-number'); return { number: await r.json() }; } ``` ```svelte <script> import { invalidate, invalidateAll } from '$app/navigation'; function rerun() { invalidate('app:random'); invalidate('https://api.example.com/random-number'); invalidate(url => url.href.includes('random-number')); invalidateAll(); } </script> <button onclick={rerun}>refresh</button> ``` **server load 不会**自动依赖 fetch 的 URL(避免泄漏凭据)——必须 `depends(url)` 显式声明。用 `untrack(fn)` 排除依赖: ```js export async function load({ untrack, url }) { if (untrack(() => url.pathname === '/')) return { message: 'Welcome!' }; } ``` **重跑 ≠ 重建组件**。`+layout.svelte` / `+page.svelte` 实例保留,只有 `data` prop 更新,组件内部 state 保留。需要强制重建 → 用 `{#key page.url.pathname}`。 ## Critical: Form actions (default/named, validation, redirects) `+page.server.js` 导出 `actions` 对象提供 `<form>` 端点。**action 总是 POST**(GET 不应有副作用)。 **Default action**: ```js // src/routes/login/+page.server.js /** @satisfies {import('./$types').Actions} */ export const actions = { default: async (event) => { /* ... */ } }; ``` ```svelte <form method="POST"> <input name="email"> <input name="password" type="password"> <button>Log in</button> </form> ``` **Named actions**:用 `?/name` 区分: ```js export const actions = { login: async (event) => { /* ... */ }, register: async (event) => { /* ... */ } }; ``` ```svelte <form method="POST" action="?/login">...</form> <form method="POST" action="/login?/register">...</form> <!-- 跨页调用 --> <!-- 同表单不同按钮 --> <form method="POST" action="?/login"> <button>Login</button> <button formaction="?/register">Register</button> </form> ``` **重要**:default + named 不能共存——若 POST 具名 action 不 redirect,`?/name` 留在 URL 里,下次 default POST 也会命中它。 **Validation errors**:`fail(status, data)` 返回 status + 数据,data 进 `form` prop / `page.form` / `page.status`: ```js import { fail } from '@sveltejs/kit'; export const actions = { login: async ({ cookies, request }) => { const data = await request.formData(); const email = data.get('email'); const password = data.get('password'); if (!email) return fail(400, { email, missing: true }); const user = await db.getUser(email); if (!user || user.password !== db.hash(password)) { return fail(400, { email, incorrect: true }); } cookies.set('sessionid', await db.createSession(user), { path: '/' }); return { success: true }; } };
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub