Agent Developer Roadmap
前端框架

SvelteKit:load、Form Actions 与 Adapter 边界

最后核验:2026-06-30|适用版本:SvelteKit 1–2(示例锁定 2.68.0)|前置知识:Svelte 5、HTTP、服务端渲染

SvelteKit 补上的应用能力

Svelte 负责组件编译和响应式更新;SvelteKit 增加文件路由、服务端 load、Form Actions、Hooks、错误边界和部署 Adapter。需要服务端数据、渐进增强表单与统一路由生命周期时选择 SvelteKit;单个客户端组件不必引入应用框架。

本仓库的知识目录是只读示例,因此使用服务端 load 和客户端 URL 状态,不伪造写接口。Form Actions 在正文中用独立局部例子解释其真实用途。

路由树与 load 执行位置

src/routes 目录形成路由树。+page.server.ts 只在服务端执行,可访问私有环境;+page.ts 可在服务端首屏和客户端导航执行;+layout 把数据和界面共享给子路由。返回值会被序列化,因此不能携带连接、函数或密钥。

load 应返回页面渲染需要的事实,不在其中写全局状态。父子 load 通过 parent() 协作,但滥用会形成瀑布;能并行的请求先启动,再等待组合。

路由节点由成对文件组成:+page.svelte 是页面组件,+page.ts/+page.server.ts 提供数据,+layout.svelte 和对应 load 包住后代,+server.ts 实现 HTTP endpoint,+error.svelte 呈现该层以下的预期/异常错误。圆括号 route group 不进入 URL,可用于选择 layout;[id][...rest][[optional]] 表示动态参数,matcher 可在匹配阶段拒绝不合法格式。

src/routes/
├── +layout.server.ts
├── +layout.svelte
├── (public)/
│   └── entries/
│       ├── +page.server.ts
│       ├── +page.svelte
│       └── [id=entry]/+page.server.ts
└── api/entries/[id]/+server.ts

layout 在客户端导航中会尽量保留,只有它读取的路由参数/URL 或声明依赖失效时才重新 load。把当前页面的临时数据放根 layout 会扩大序列化与失效范围;把认证用户放最接近根的 +layout.server.ts 则可让后代通过 parent() 共享。+page.svelte$props() 获取 { data, form },SvelteKit 生成的 PageData 让类型沿路由树合并。

链接使用正常 <a href>,SvelteKit 拦截同源导航、预加载代码/数据并保留 Web 语义。data-sveltekit-preload-data 可调整预取时机;昂贵、强权限或写敏感页面不应无差别在 hover/tap 前取数。状态需要可分享就进入 query;只属于当前 history entry 的 UI 状态可通过导航 state 表达。

高级路由不能替代资源校验。matcher 只说明字符串形式像 ID,load 仍需确认记录存在且当前用户可见。reroute/rewrite 改变内部解析时,要验证编码、大小写、base path 和反向代理后的 URL,避免展示路由与授权资源不一致。

通用 load 与服务端 load

通用 load 适合可在两端运行、只依赖公开数据的逻辑;服务端 load 适合数据库、私有配置、认证和文件系统。两者都能使用 fetch,SvelteKit 会在服务端内部请求和水合期间进行必要集成。

<!-- +page.svelte -->
<script lang="ts">
  let { data } = $props();
  let query = $state('');
  let filtered = $derived(search(data.entries, query));
</script>

外部响应在进入 PageData 前仍要验证。TypeScript 生成的 $types 只验证编译期调用,不验证网络或数据库内容。

Server load 的 event 包含 locals、cookies、params、request、route、setHeaders 和增强版 fetch,适合私有依赖;universal load 只拿可在浏览器安全复现的子集。初次 SSR 时两类 load 在服务器运行,后续导航时 +page.ts 可在浏览器运行,而 +page.server.ts 通过 SvelteKit 数据请求在服务器运行。

// src/routes/entries/[id]/+page.server.ts
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ locals, params, depends }) => {
  const user = await locals.requireUser();
  depends(`app:entry:${params.id}`);

  const entry = await locals.db.entry.findVisible({
    id: params.id,
    userId: user.id,
  });

  if (!entry) error(404, 'Entry not found');
  return { entry: toPublicEntry(entry) };
};

Server load 返回值通过 devalue 协议序列化,能处理部分比 JSON 更丰富的值,但数据库连接、闭包、任意类实例和秘密都不应跨边界。最稳妥的契约仍是最小 DTO。HTTP response 的 Set-Cookie 应通过 cookies API 设置;setHeaders 适合 Cache-Control 等单值头,同一响应头被不同 load 重复设置会冲突。

SvelteKit 的增强 fetch 在服务器可继承受控 cookie/authorization、调用内部 +server 路由而不经过网络,并把 SSR 使用的响应嵌入页面避免水合重复获取。但“内部调用”仍需要 handler 进行授权;若 load 与 endpoint 同仓,直接调用纯服务层通常比自请求更清楚。外部响应必须限制状态、大小、超时和重定向。

父子 load 可以并行开始。只有调用 await parent() 后才需要父数据;若先等 parent 再发独立请求,就人为制造瀑布。数据库查询也可在等待其他 promise 前启动。导航到新 URL 时,框架根据 load 实际读取的 params/url 属性以及显式依赖决定重跑范围,而不是每次重跑整棵树。

Form Actions 与渐进增强

+page.server.tsactions 接收原生表单提交,适合登录、资料编辑等写操作。没有 JavaScript 时浏览器完成标准 POST;use:enhance 可在客户端保持页面体验,同时沿用同一服务端验证。

结果服务端做法页面行为
字段无效fail(400, fields)保留输入并显示错误
未授权重定向或明确错误不执行写入
写入成功重定向或返回结果重新运行相关 load
系统故障抛出错误进入错误边界

Action 必须重新验证所有输入、执行授权并处理重复提交;客户端约束和隐藏按钮不是安全边界。Form Actions

默认 action 用 actions.default,多个命名 action 通过 ?/save?/delete 等提交;同一表单不要在 action URL 和任意开放 redirect 之间拼接用户输入。失败数据会成为页面 form prop,应该只返回安全字段、字段级错误和必要的回显,密码、token、数据库错误与堆栈不能进入。

export const actions = {
  rename: async ({ request, locals, params }) => {
    const user = await locals.requireUser();
    const form = await request.formData();
    const title = String(form.get('title') ?? '').trim();

    if (title.length < 1 || title.length > 80) {
      return fail(400, { title, errors: { title: '标题需为 1–80 个字符' } });
    }

    const updated = await locals.db.entry.renameOwned({
      id: params.id,
      userId: user.id,
      title,
    });
    if (!updated) error(404, 'Entry not found');

    redirect(303, `/entries/${params.id}`);
  },
} satisfies Actions;

POST 成功后通常用 303 redirect 遵循 Post/Redirect/Get,刷新不会重复提交。use:enhance 只适用于 action POST 表单,它拦截提交并更新 form/页面数据;可用 callback 控制 pending、取消和 applyAction,但应保持无 JavaScript 时仍能完成核心流程。文件上传要同时限制单文件/总大小、类型和存储路径,不能把浏览器 accept 当验证。

双击、重试、网络断开都可能重复到达服务器。创建/支付操作使用幂等键、唯一约束和事务;按钮 pending 仅改善体验。CSRF 防护依赖 cookie SameSite、Origin 检查和部署 origin 配置,仍需按威胁模型验证跨站表单与代理 header。

失效、依赖键与请求取消

depends(key) 声明 load 依赖,invalidate(key) 重新运行匹配的加载;invalidateAll 范围更大,不应作为默认刷新。URL 参数变化自动使读取它们的 load 失效。写后刷新要针对受影响数据,不清空整个应用状态。

依赖可以是 fetch URL,也可以是 app: 等自定义 URI。key 应映射领域数据:详情依赖 app:entry:42,列表依赖 app:entries:user-7。Action 写入后可由默认增强流程重跑当前页面 load;跨组件实时更新或命令式请求则用精确 invalidateinvalidateAll 会放大服务端与网络负载,也会让不相关表单和 layout 数据闪烁。

await invalidate((url) => url.pathname.startsWith('/api/entries'));
await invalidate(`app:entry:${entryId}`);

只在 load 中实际 fetchdepends 声明过的依赖才有清楚关系。自定义模块变量读取不会被框架自动追踪。失效也不是 HTTP/CDN 缓存 purge:load 重跑后 fetch 仍可能命中上游缓存,必须分别定义浏览器、CDN、endpoint 和数据源一致性。

离开导航时框架会管理相关请求,但自建订阅、计时器和浏览器请求仍需清理。取消属于正常生命周期,不显示成网络故障。

Hooks、locals 与信任边界

服务端 handle hook 包围请求,可解析会话并写入 event.localshandleFetch 调整服务端请求。Hooks 是请求级横切边界,不用于保存跨请求可变用户状态。认证信息从可信 cookie 或 header 解析,授权仍在每个数据/Action 边界执行。

handle 可组合 request ID、会话解析、locale 与安全响应头,再调用 resolve(event)。它适合“每个请求都要做”的工作,不适合把整个业务写成全局 middleware。locals 每请求新建,类型在 app.d.ts 声明;放入 user 时只保存必要身份/能力,不把原始 token 到处传。

export const handle: Handle = async ({ event, resolve }) => {
  event.locals.requestId = crypto.randomUUID();
  event.locals.user = await readSession(event.cookies);

  const response = await resolve(event);
  response.headers.set('x-request-id', event.locals.requestId);
  return response;
};

handleFetch 只影响服务端 load/action/endpoint 发出的 fetch,可为已知上游转发特定 header;绝不能把来访请求的 cookie、Authorization、Host 和 forwarded headers 原样转给任意目标。handleError 负责记录未知异常并返回给页面的安全错误形状,生产中用 request ID 关联服务端堆栈。

认证 cookie 应设置 httpOnlysecure(生产)、合理 sameSite 与 path;客户端可读状态不能充当会话凭证。根 layout 展示登录用户可以来自 locals,但每个 server load、action 和 +server handler 仍需在访问资源时授权,避免只保护导航入口。

SSR、CSR、预渲染与 Adapter

路由可选择 SSR、CSR 和 prerender。预渲染要求构建时可枚举且不依赖请求私有数据;关闭 SSR 会失去服务端首屏;关闭 CSR 会失去客户端导航和交互增强。选择应按路由约束,而不是全站一个开关。

Adapter 把构建结果映射到 Node、serverless、edge 或静态环境。文件系统、流、连接和环境变量能力取决于目标 Adapter;adapter-auto 适合示例和平台自动检测,生产应锁定明确目标。Adapters

page options 可在 layout 向下继承,也可由页面覆盖:prerender 在构建时输出静态内容,ssr 控制首次服务端渲染,csr 控制客户端运行/导航,trailingSlash 影响路径规范。ssr = false 不是解决水合 bug 的正常方案;它把错误隐藏为客户端空壳。csr = false 适合真正无客户端交互页面,但普通链接仍按文档导航。

预渲染 crawler 从入口链接发现路径,动态参数需 entries 或可达链接。构建时没有真实请求用户,任何依赖 cookie 的内容都不应预渲染。预渲染输出若调用外部 API,要固定超时、构建凭证和失败策略,避免构建环境成为 SSRF/供应链入口。

Adapter 决定 request 如何进入应用、环境变量从哪里读、流是否保留、静态资产/缓存头如何发布。Node adapter 需要正确配置 ORIGIN 或可信代理协议/host 规则;serverless 要考虑冷启动与每实例连接池;edge 要验证 Node API 和包兼容;static adapter 无运行时 Action/server load。生产验收必须在最终 Adapter 产物上执行伪造 Host、base path、流式响应、大 body、超时和进程重启测试。

客户端导航、状态与性能

goto 执行编程导航,beforeNavigate/onNavigate/afterNavigate 处理导航生命周期。离开未保存表单可在 beforeNavigate 提示,但浏览器和跨站导航限制不同;不要阻止所有导航来掩盖状态设计。pushState/replaceState 可建立 shallow routing state,URL 与 history state 的可恢复性必须明确。

$app/statepagenavigatingupdated 提供当前 URL/data/form 和导航状态。不要把 page.data 复制进另一份 $state 后长期同步;可编辑草稿应明确初始化时机和提交/回滚,纯展示直接读取 PageData。

性能优化覆盖四层:

  1. load 中并行独立 I/O,避免不必要 parent() 瀑布,并裁剪 PageData。
  2. 设计精确 invalidation,防止每次写入重跑根 layout 和全部 endpoint。
  3. 预加载只用于高概率导航,检查数据与代码请求;大型非首屏组件动态导入。
  4. 选择符合内容陈旧度的 CDN/endpoint cache,私有响应设正确 private/no-store
  5. 用生产 Adapter 的 Server-Timing、浏览器 Network/Performance 和 Svelte Devtools 分别定位服务端等待、传输、水合与 DOM 成本。

常见问题可按证据定位:首次 HTML 无数据先看 server load;软导航重复请求看依赖与 parent;Action 成功页面陈旧看增强结果和失效;开发正常生产 origin 错看 Adapter/代理;水合差异看服务端 PageData、确定性 ID/时间和第三方 DOM 修改。不要把所有故障归为“Svelte 响应式没更新”。

知识目录示例的运行证据

SvelteKit 示例+page.server.ts 中导入并验证共享目录,页面使用 Runes 处理搜索、详情、选择和 URL。只读需求没有 Form Action。

运行效果SvelteKit 知识目录运行效果
新窗口打开

正在加载示例……

pnpm --filter @roadmap/sveltekit-slice test
pnpm --filter @roadmap/sveltekit-slice preview --port 4207

输入:访问 http://127.0.0.1:4207/,查看源代码,搜索 load,选择两项并刷新。

预期结果:模型测试与生产构建通过;服务端 HTML 包含八项目录,客户端无水合警告,URL 恢复查询与选择。

错误定位、重大演进与严重安全修复

服务端终端检查 load、Action 和 Hook,浏览器 Network 检查数据请求与导航,Svelte Devtools 检查客户端组件。错误要区分 error() 的预期 HTTP 故障和未知异常;生产响应不泄露堆栈或私有 locals

SvelteKit 1 → 2 的主线是升级 Vite、Node 与路由 API,并为 Svelte 5/Runes 建立兼容;后续 2.x 增加 Remote Functions 等能力,但实验功能的协议与安全面不能视为普通 minor API。按官方迁移工具逐步升级,先处理路由与 $types 诊断,再验证 SSR/CSR、prerender 和目标 Adapter。

高风险变化失败表现验证方式
load/Action 返回值跨序列化边界类实例、函数、密钥丢失或泄露查看 PageData 响应和 HTML,不只看组件类型
invalidation 范围变化写后数据陈旧或全站重复请求为每个 depends key 记录触发者并检查 Network
Adapter/ORIGIN 配置变化生产 SSR 构造错误 origin、流或缓存头在目标 Adapter 的生产预览发送伪造 Host 与代理头
Remote Functions 实验协议反序列化放大、批处理串数据限制 body/并发,恶意载荷测试,不需要时不启用

2026-01 的 GHSA-j62c-4x62-9r35(CVSS 8.4)影响 @sveltejs/kit 2.19.0–2.49.4 和 adapter-node 5.4.1–5.5.0:存在预渲染路由且 Node Adapter 未配置可信 ORIGIN/Host 校验时,可造成 SSRF、进程退出,甚至经 CDN cache poisoning 形成 XSS;修复为 Kit 2.49.5、adapter-node 5.5.1。同日 GHSA-j2f3-wq62-6q46 指出实验 Remote Form 二进制反序列化可由极小前缀诱导大内存分配,Kit 2.49.0–2.49.4 受影响,2.49.5 修复。本示例 2.68.0 已越过修复线。预渲染 SSRF/DoSRemote Form 内存放大

修复后仍要把 ORIGIN、反向代理可信 header、请求体上限和平台超时写入部署契约。预渲染不是天然安全的静态过程;构建 crawler 与运行时错误页都可能发请求。验收标准是能解释每个 load 的执行位置、失效键、序列化边界与部署前提,并用恶意 Host、超大长度前缀和不可序列化值验证失败路径。

SvelteKit 官方资料

  • Routing:文件路由与匹配;核验于 2026-06-30。
  • Loading data:通用/服务端 load 与失效;核验于 2026-06-30。
  • Hooks:请求生命周期与 locals;核验于 2026-06-30。
  • Page options:SSR、CSR 与预渲染;核验于 2026-06-30。
在 GitHub 上编辑此页

本页目录