原文作者:Svelte 文档团队与 sveltejs/kit 贡献者(页面无个人署名)。中文翻译与技术整理:未完纪。核验日期:2026-10-05。

SvelteKit 3 清理了一批旧接口,把项目配置从 svelte.config.js 移到 Vite 插件,并提高最低依赖版本。迁移的重点不是只改导入路径:表单导航、错误状态码、跨源信任、Cookie 作用域和部署适配器都有可观察的行为变化。本文按官方迁移页完整翻译整理,并把示例中的安全边界单独说明。
版本基准为 2026-10-05 读取的官方页面与同页 GitHub Markdown 快照。官方建议先升级到最新 2.x,借助有针对性的弃用警告清理项目,再迁移到 3.0。许多机械修改可以通过下面的工具完成,但它不能替代应用回归检查:
npx sv migrate sveltekit-3
编辑补充:npx 可能下载并执行包,迁移工具会改动项目文件。应在已经提交或备份的工作副本中审查包来源、锁定适当工具版本并检查差异。本文未执行该命令,也没有确认某个项目已升级成功。
依赖与配置入口
| 依赖 | 官方迁移页给出的最低版本 |
|---|---|
| Node.js | 22.17 |
| TypeScript | 6 |
| Svelte | 5.57.1 |
| Vite | 8.0.12(首个捆绑稳定 rolldown v1 的 Vite 8 版本) |
| @sveltejs/vite-plugin-svelte | 7 |
修改 package.json 中的版本后,运行项目使用的包管理器安装命令。最低要求和实际锁文件应一起核对,不应仅更新一个框架包而忽略构建链。
svelte.config.js 不再是受支持的配置入口。原来 config.kit 下的选项,移到 vite.config.js 内 sveltekit 插件的顶层,与 compilerOptions 等选项并列。原文示例启用了 experimental.async;下面仅保留稳定配置的结构,若应用依赖实验性异步编译,再明确启用对应选项:
import { defineConfig } from 'vite';
import { sveltekit } from '@sveltejs/kit/vite';
import adapter from '@sveltejs/adapter-auto';
export default defineConfig({
plugins: [sveltekit({ adapter: adapter() })]
});
插件选项也供编辑器等 Svelte 集成工具读取。不属于 SvelteKit 的选项会转交 vite-plugin-svelte,例如 inspector 可直接写在插件选项中;experimental 命名空间由双方共享。官方页面说明,通过 Vite 配置 SvelteKit 的能力在 2.62 已加入,适合先在 2.x 清理配置。
| 旧选项或变化 | 迁移方式 |
|---|---|
| files.lib | 移除;用 package.json 的 #lib 子路径导入。 |
| experimental.handleRenderingErrors | 移除;渲染错误现在总会处理。 |
| experimental.instrumentation | 移除;存在对应文件就自动启用。 |
| experimental.tracing | 移到顶层 tracing。 |
| vitePlugin | 移除;其选项直接传入插件。 |
| preloadStrategy | 移除;现在总使用 modulepreload。 |
| prerender.origin | 由 paths.origin 替代。 |
| csrf.checkOrigin | 由 csrf.trustedOrigins 替代,不能再关闭 CSRF 检查。 |
| output.linkHeaderPreload | 新增;选择用 Link HTTP 头预加载资源。为避免头部过大,v3 默认在 HTML 中放 link 元素。 |
| paths.origin | 新增;在反向代理等无法可靠从请求头推断公开 origin 的场景,指定应用的外部 origin,用于表单与远程函数 CSRF 检查;也替代 adapter-node 的 ORIGIN 环境变量。 |
| version.pollInterval | 默认改为一小时;定期发现部署更新并设置 updated.current。 |
从 $lib 迁往 #lib,并整理模块导入
SvelteKit 不再自动生成 $lib 别名。请在 package.json 的 imports 字段声明 Node 原生子路径导入;Vite 与 TypeScript 都能原生解析。原文默认目录对应如下:
{
"imports": {
"#lib": "./src/lib/index.js",
"#lib/*": "./src/lib/*"
}
}
然后把代码中的 $lib 改为 #lib,并补齐实际文件扩展名。例如 $lib/foo 改为 #lib/foo.js;TypeScript 项目应按实际模块路径与解析规则处理 .ts 或 .js。不要在没有 index.js 的项目中无条件照抄根别名,应确认所声明入口存在。
| 原模块或能力 | 新位置及行为 |
|---|---|
| $app/environment | $app/env;现在 service worker 也能导入。 |
| $app/manifest | 新增,提供应用元数据;任何应用代码及 service worker 都可用,包含离线缓存所需资源信息。 |
| $app/stores | 已移除;改用 $app/state 的 Svelte 5 细粒度状态,读取 page、navigating、updated 时不再加 $ 前缀。 |
| $env/… | 弃用;迁往 $app/env/private 和 $app/env/public。私密配置不能因此改成公开变量。 |
| $app/service-worker | 新增,为 src/service-worker/index.ts 提供有类型的 service worker 上下文,需对应独立 tsconfig。 |
| $service-worker | 已移除;version 从 $app/env 获取;assets、immutable、prerendered 从 $app/manifest 获取;路径由 $app/paths 的公开接口处理。 |
勘误说明:迁移页的 $service-worker 小节写了从 $app/paths 导入 resolved;同页前文与当前 $app/paths 参考页实际列出的是 resolve,resolved 是示例变量名。本文按参考页使用 resolve,并保留这处差异记录,避免复制不存在的导出。
增强表单和导航:检查可见行为变化
使用 use:enhance 的表单,如果 action 指向另一页面,现在提交后会导航到那个页面,以便与未增强的原生表单行为一致;过去可能停留在当前页。跨页面提交后依赖当前组件继续存在的逻辑,需要相应复查。
浅路由的 pushState 与 replaceState 被弃用,统一使用 goto:
import { goto } from '$app/navigation';
goto('/foo', { shallow: true, state });
goto('/bar', { shallow: true, replace: true, state });
state 代表应用要保存的页面状态。新增 persistState: true 可以在刷新后重新应用 page.state。浅路由现在也会触发 beforeNavigate、onNavigate 和 afterNavigate;如果原来只希望处理完整导航,可以检查传入对象的 shallow 属性过滤。
invalidateAll 被弃用,改用 refreshAll。后者不会把 page.state 重置为空对象,通常更符合浅路由需求。现在在导航进行期间调用 invalidateAll() 或 invalidate(…),也不会中止正在进行的导航。
| goto 旧选项 | 新选项 |
|---|---|
| invalidateAll | refreshAll |
| keepFocus: true 与 noScroll: true | 合并为 reset: false |
| replaceState | replace |
goto 传入不能匹配应用内部路由的 URL 时会拒绝 Promise,与外部 URL 的既有行为一致。要导航到外部地址,原文建议 window.location.href = url;编辑补充要求先验证不可信 url 的协议和目标,不把该语句视作输入校验。导航事件的 delta 现在只在 popstate(前进、后退)时有值,其他导航为 undefined。
preloadData 不再把加载失败伪装为 type: loaded、状态 200,而是返回 { type: error, status, error };redirect 结果也包含正确状态码。消费结果的代码必须显式增加 error 分支:
const result = await preloadData(url);
if (result.type === 'loaded') {
// 使用成功加载的数据
} else if (result.type === 'error') {
// 按 result.status 与 result.error 处理失败
} else if (result.type === 'redirect') {
// 按应用策略处理重定向
}
路径解析与页面状态
$app/paths 已移除 base、assets 和 resolveRoute。路由 ID 与参数交给 resolve;静态资源交给 asset,不再手工拼 base 或 assets。
import { asset, resolve } from '$app/paths';
const pathname = resolve('/blog/[slug]', { slug });
const file = asset('foo.png');
const fixedPath = resolve('blog/hello-world');
Pathname 与 Asset 类型分别更名为 Path 和 AssetPath,而且这些类型对应的路径不再以 / 开头。因此静态资源使用 asset('foo.png');直接传给 resolve 的 pathname 使用 blog/hello-world。只有路由 ID 保留开头的 /,例如 /blog/[slug]。service worker 现在可直接使用 $app/paths,不必再借助已移除的 $service-worker.base。
page.url 现在是 ReadonlyURL,searchParams 也是只读的 ReadonlyURLSearchParams。直接赋值 pathname 或调用 page.url.searchParams.set 会产生类型错误。需要构造新地址时,先复制,再修改副本:
const url = new URL(page.url.href);
url.searchParams.set('q', 'svelte');
updated.current 在发现新部署后变为 true。v3 除了手动检查和失败导航,还会在从服务器取数据的导航、远程函数调用、窗口重新可见或获得焦点、以及默认每小时轮询时检测版本。使用 Vercel skew protection 等机制时,导航或远程函数仍可能命中旧部署,造成被动检测的假阴性;轮询与窗口事件触发的检查会绕过该机制,仍可工作。
TypeScript 与公开 API 的新位置
项目 tsconfig.json 改为继承 $app/tsconfig,而不是 ./.svelte-kit/tsconfig.json。新基配置不带 include/exclude,需要项目自己声明;它包含 isolatedModules、verbatimModuleSyntax 等必要选项,以及可被项目覆盖的建议选项。
{
"extends": "$app/tsconfig",
"include": ["src", "test", "*"],
"exclude": ["src/service-worker"]
}
service worker 必须作为独立 TypeScript 项目,否则 fetch 事件等类型会不正确。在主配置中排除它,并添加 src/service-worker/tsconfig.json:
{ "extends": "$app/tsconfig/service-worker" }
| API/类型 | 迁移处理 |
|---|---|
| error、isHttpError、redirect、isRedirect | 改用公开类型;不再依赖 @sveltejs/kit/internal 的 HttpError/Redirect 内部类或 instanceof 判断,使用公开判断函数。 |
| json 与 text | 弃用;改为 Response.json(…) 和 new Response(text)。 |
| defineParams 与相关类型 | 移到 @sveltejs/kit/params。 |
| EnvVarConfig 等环境类型及 defineEnvVars | 移到 @sveltejs/kit/env;defineEnvVars 不再从 hooks 导入。 |
| Handle 等 hooks 类型 | 移到 @sveltejs/kit/hooks。 |
| RemoteQuery、RemoteForm、RemoteCommand 等 | 与远程函数一起位于 $app/server。 |
| @sveltejs/kit/node 的 getRequest、setResponse | 变为同步,移除调用处的 await。 |
| @sveltejs/kit/node/polyfills | 已移除;删除导入。旧 Node 版本使用的 adapter-node 与 adapter-netlify 全局 shim 也不再需要。 |
安全选项:信任列表替代关闭检查
csrf.checkOrigin 已删除,CSRF 防护始终启用。原先通过 checkOrigin: false 关闭检查的项目,应该把确有需要的外部来源写入 csrf.trustedOrigins。该列表是允许跨源表单提交的信任边界,而不是任意填入一个域名让错误消失。paths.origin 应反映应用真正对外的 origin,反向代理配置不正确会影响这些校验。
sveltekit({
csrf: {
trustedOrigins: ['https://trusted-site.com']
}
})
上面域名来自原文示例,使用前必须换成经过确认的可信来源;没有跨源需求就不添加。跨源且会修改状态的请求如果省略 Content-Type,现在会被当作 CSRF 拒绝。应让表单提交携带正确 Content-Type,或只在确有信任关系时配置 trustedOrigins。Content-Type 不是替代身份验证的凭证。
开发环境静态资源不再由 SvelteKit 一律附加 access-control-allow-origin: *;CORS 交给 Vite 中间件。原文演示 server.cors.origin: *,会扩大任意网页读取开发资源的范围。本文不把通配符作为默认配置;确有开发跨源需求时,只列出必要来源,例如本地另一个已知端口:
export default defineConfig({
server: {
cors: { origin: ['http://localhost:5174'] }
}
});
这是与原文有意不同的收紧示例,端口须符合实际开发环境;未对任何开发服务器实测。
Cookie 与错误处理
框架升级到 cookie v2:Cookie 名称只允许 ASCII,á 等非 ASCII 字符也会拒绝;CookieSerializeOptions 改名 SerializeOptions,CookieParseOptions 改名 ParseOptions。设置 Cookie 时现在可省略 path,默认 /,覆盖整个站点;以前必须显式提供路径。如果应用只需要某个路径范围,应保留显式 path。路径作用域变化本身不是身份授权边界,仍应检查服务端权限。
| 变化 | 迁移重点 |
|---|---|
| App.Error 总包含 status | 除 message 与项目自定义字段外,错误对象自带触发错误的 HTTP 状态码。 |
| error(…) 参数 | 第二参数必须是消息字符串;自定义字段放第三参数,不再把含 message 的对象放第二参数。 |
| handleValidationError 删除 | 验证错误交给 handleError,kind 为 validation;通用 error 的 status/message 可安全返回,issues 单独提供给日志或自定义处理。 |
| handleError 接收全部错误 | 包括 error(…) 创建的预期错误,日志计数与告警规则需防止把预期 4xx 全当内部故障。 |
| handleError 可影响状态码 | 返回 App.Error 时可同时返回 status,以控制错误页面 HTTP 状态。 |
| 渲染异常 | 总是先经 handleError,再交最近的错误边界;每个 +error.svelte 自动形成边界。客户端异步 handleError 需要 compilerOptions.experimental.async,才可在渲染中等待。 |
| 增强表单 fail(…) | HTTP 状态码现在采用 fail 的状态,而不一律为 200;use:enhance 回调和断言需要调整。 |
| Sourcemap | 默认生成,并用于错误堆栈;生产适配器必须避免破坏映射,若重新打包必须生成正确新映射。原文指出第一方适配器支持仍在完善,不能假设所有部署效果相同。 |
静态安全补充:验证 issues 和堆栈可能带入内部结构或输入内容,不能为了方便调试直接回传给客户端。保留用户可见的有限错误信息,在受控日志中处理细节。
参数匹配器与可观测性
参数匹配器不再散落在 src/params 目录。将全部匹配器放入 src/params.ts 或 src/params.js,并用 defineParams 声明。匹配器可以是 Standard Schema,也可以是函数:匹配时返回解析后的值,不匹配返回 undefined。原文以 Valibot 的字符串转数字展示 schema 形式;若业务要求整数,还必须按该校验库的能力增加整数约束,不能只因匹配器叫 integer 就假定限制已经成立。
import { defineParams } from '@sveltejs/kit/params';
export const params = defineParams({
fruit: (param) => {
if (param === 'apple' || param === 'orange') return param;
}
});
存在 src/instrumentation.server.js 时,服务端 instrumentation 现在自动启用。OpenTelemetry tracing 仍需选择开启,在插件顶层配置 tracing: { server: true }。迁移时删除旧 experimental.instrumentation,并把 experimental.tracing 移到这里。
部署适配器:平台差异必须单独核查
第一方适配器的新版本均要求 SvelteKit 3。除了框架本身,部署工具的版本和输出约定也必须一起更新。
| 适配器 | 变化 |
|---|---|
| adapter-cloudflare | Cloudflare API 不再放在 platform:env、waitUntil 及其他 ctx 能力按 Worker 接口从 cloudflare:workers 获取;cf 位于 Request 对象;caches 为全局变量。安装 wrangler 并运行 wrangler types 生成类型,最低 wrangler 为 ^4.67.0;@cloudflare/workers-types 也已升级。 |
| adapter-node | 改用 rolldown 打包;ORIGIN 环境变量移除,改设 paths.origin;静态资源按构建时列表提供,之后添加的文件不会被提供,替换文件仍保留旧大小与 ETag;运行期配置应采用环境变量。静态 ETag 改为内容哈希,不再发送 Last-Modified;仅 GET/HEAD 能取静态资源,其他方法为 405。 |
| adapter-netlify | 输出遵守稳定 Netlify Frameworks API;CLI 部署或预览需要 17.31.0+;edge function 构建目标为 es2022;publish 目录改成适配器选项,不再读取 netlify.toml 中该项。原文全局安装 latest 仅为更新方法,实际项目应核对版本。 |
| adapter-vercel | 不再支持 edge runtime。 |
Cloudflare 迁移示意如下;它们是独立上下文中的用法,不是一个可直接复制执行的完整请求处理器:
import { env, waitUntil } from 'cloudflare:workers';
const value = await env.KV.get('key');
// 在请求处理器中
const { country } = request.cf;
// Worker 的全局缓存接口
const myCache = await caches.open('foo');
const cached = await myCache.match(request);
适配器作者需要迁移的 API
- 适配器可以通过附加插件扩展 Vite 配置。
- builder.config.kit 不再存在,配置位于顶层。
- builder.createEntries 删除;直接使用 builder.writeClient、writeServer、writePrerendered。
- builder.compress 返回压缩后文件列表。
- builder.mkdirp 与 builder.rimraf 弃用,改用 node:fs 对应方法。
- builder.generateManifest 删除;生成服务实例使用 builder.generateServerInstance,读取 manifest 使用 builder.manifest。
- 服务端输出导出的 Server 类弃用;使用 generateServerInstance 生成的 server 对象。
使用 builder.instrument 的适配器,还要先于任何打包步骤生成环境初始化模块,把返回的模块作为打包或文件追踪入口之一,并把最终路径传给 instrument:
const initializer = builder.createInstrumentationInitializer({
outputDirectory: temporary_directory
});
// 在这里将 initializer 纳入打包/文件追踪,并使用其最终路径
builder.instrument({ entrypoint, instrumentation, initializer });
如果运行时不是通过 process.env 暴露环境变量,可传入一个默认导出平台环境的模块内容。原文 Cloudflare 示例在 createInstrumentationInitializer 的 environment 选项中放入从 cloudflare:workers 导入 env 并 export default env 的模块字符串。该字符串应是受信任的构建期代码,不应拼接用户输入。
响应与仅服务端模块
+server.js 返回 204 或其他空的 2xx 响应时,现在按 HTTP 规范保留空响应体,不再包一层 SvelteKit envelope;调用端不能无条件继续读取 JSON。传给 handle 的 resolve 类型现在总是 Promise<Response>,不再是 MaybePromise<Response>。
仅服务端模块按文件名中的 server 段识别:stuff.server.ts、stuff.server.test.ts 和 server.ts 都属于服务端模块,过去 server.ts 本身不在这一规则中。目录规则也扩展了:除 src/routes 与 static 目录的例外外,项目中任何 server 目录都得到这一待遇,不再限于 src/lib/server。迁移时检查客户端导入链,避免把服务器依赖误接到浏览器构建。
远程函数仍是实验功能
远程函数仍需同时选择启用 experimental.remoteFunctions 和 compilerOptions.experimental.async;本文不把它们作为升级必须开启的稳定功能。示意配置如下:
sveltekit({
compilerOptions: { experimental: { async: true } },
experimental: { remoteFunctions: true }
})
文件名规则与服务端模块类似:包含 remote 段的 stuff.remote.ts、stuff.remote.test.ts、remote.ts 都视为远程模块;未开启实验选项却存在这类文件会报错。在 remote query 内访问 event.url、event.params 或 event.route 现在直接抛错,因为远程函数可从任意位置调用,这些页面属性没有稳定含义;把需要的值作为参数显式传入。
query、live query、form 和 prerender 等资源的 error 类型改为 App.Error | undefined,不再是 any,因为错误始终经过 handleError。表单控件必须使用当前 form 对象关联字段的 as(…) 返回属性;仅手工写 name="message" 会被拒绝:
<input {...myform.fields.message.as('text')}>
其余会影响回归检查的行为
指向当前位置的链接现在调用 refreshAll(),不会再什么也不做。data-sveltekit-* 链接属性不再接受 off,改用 false,例如 data-sveltekit-preload-data="false"。service worker 也改为以 type: module 打包并注册。
外部 redirect 必须显式通过 external 选项允许。原文展示 external: true,允许任意外部 URL,但仍阻止 javascript:;另一种形式是允许的 origin 数组。原文特别说明数组形式可包含 javascript:,因此不能把“使用数组”本身误认为已排除危险协议。编辑建议仅列经过核实的 HTTPS origin,单独校验不可信跳转目标,并避免把任意用户输入传给允许全部目标的重定向。
// 编辑收紧示例:仅允许已核实的外部 origin
redirect(307, 'https://example.com', {
external: ['https://example.com']
});
路由通用 +page.js 或 +layout.js 导出的 config,现在优先于对应 +page.server.js 或 +layout.server.js 中的 config,与其他页面选项的优先级一致。两处都导出时,应把权威配置放到通用文件,或合并为一处。
迁移后的检查范围与静态审查结论
需要围绕应用真实行为复查:跨页面增强表单、浅路由状态与 hooks、预加载失败、只读 URL、带 base path 的资源与路由、Cookie 路径、预期与非预期错误、fail 状态、反向代理 origin、远程函数验证和适配器静态资源。以上是待执行的检查范围,不是本文完成的测试清单。
本次只做静态审查,已标出开发 CORS 通配符、外部重定向开放范围、验证细节泄露以及运行包迁移命令的真实影响。没有发现硬编码秘密;私密环境变量与 server 模块仍需按实际项目检查。未发现其他问题不等于无漏洞。
MIT 许可证原文
Copyright (c) 2020 [these people](https://github.com/sveltejs/kit/graphs/contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
来源、署名与许可
SvelteKit 仓库采用 MIT License,Copyright (c) 2020 SvelteKit contributors。完整版权和许可文字已保留在正文中。本稿为中文翻译整理,示例调整及源文勘误已明确标记。
保留作者与适用许可证。本文调整与安全补充均已说明,未执行原文应用代码或命令。












暂无评论内容