Migrating to SvelteKit v3
SvelteKit 3 removes some legacy features, moves configuration out of svelte.config.js into the Vite plugin, and raises the minimum version of certain dependencies. For many of these breaking changes, you can automatically migrate:
npx sv migrate sveltekit-3We recommend upgrading to the most recent 2.x version before upgrading to 3.0 so that you can take advantage of targeted deprecation warnings.
Updated dependencies
SvelteKit 3 requires the following minimum versions:
- Node v22.17
- TypeScript v6
- Svelte v5.56.4
- Vite v8.0.12 (the first Vite 8 release bundling stable
rolldownv1) @sveltejs/vite-plugin-sveltev7
Update the versions in your package.json and run your package manager's install command.
Configuration
svelte.config.js is no longer supported
Instead of declaring project configuration in svelte.config.js, it must now be passed to the sveltekit Vite plugin in vite.config.js. Options that previously lived under config.kit.* are now top-level plugin options, alongside things like compilerOptions:
import { function defineConfig(config: UserConfig): UserConfig (+5 overloads)Type helper to make it easier to use vite.config.ts
accepts a direct
{@link
UserConfig
}
object, or a function that returns it.
The function receives a
{@link
ConfigEnv
}
object.
defineConfig } from 'vite';
import { function sveltekit(config?: KitConfig & Omit<Options, "onwarn"> & Pick<SvelteConfig, "vitePlugin">): Promise<Plugin[]>Returns the SvelteKit Vite plugins.
Any options that don't belong to SvelteKit are passed through to vite-plugin-svelte.
Since version 3.0.0 you must pass configuration directly.
Since version 2.62.0 you can pass configuration directly, in which case svelte.config.js is ignored.
sveltekit } from '@sveltejs/kit/vite';
import function adapter(): Adapteradapter from '@sveltejs/adapter-auto';
export default function defineConfig(config: UserConfig): UserConfig (+5 overloads)Type helper to make it easier to use vite.config.ts
accepts a direct
{@link
UserConfig
}
object, or a function that returns it.
The function receives a
{@link
ConfigEnv
}
object.
defineConfig({
UserConfig.plugins?: PluginOption[] | undefinedArray of vite plugins to use.
plugins: [
function sveltekit(config?: KitConfig & Omit<Options, "onwarn"> & Pick<SvelteConfig, "vitePlugin">): Promise<Plugin[]>Returns the SvelteKit Vite plugins.
Any options that don't belong to SvelteKit are passed through to vite-plugin-svelte.
Since version 3.0.0 you must pass configuration directly.
Since version 2.62.0 you can pass configuration directly, in which case svelte.config.js is ignored.
sveltekit({
compilerOptions?: Omit<CompileOptions, "filename" | "format" | "generate"> | undefinedThe options to be passed to the Svelte compiler. A few options are set by default,
including dev and css. However, some options are non-configurable, like
filename, format, generate, and cssHash (in dev).
compilerOptions: { experimental?: {
async?: boolean;
} | undefinedExperimental options
async?: boolean | undefinedAllow await keyword in deriveds, template expressions, and the top level of components
KitConfig.adapter?: Adapter | undefinedYour adapter is run when executing vite build. It determines how the output is converted for different platforms.
function adapter(): Adapteradapter()
})
]
});See the configuration docs for further examples.
Removed options
The following options are obsolete and should be removed from your vite.config.js:
files.lib(details)experimental.handleRenderingErrorsis no longer required (details)experimental.instrumentationis no longer required (details)experimental.tracingis now a top leveltracingoption (details)preloadStrategyis removed —modulepreloadis now supported everywhere and so is always usedprerender.originis removed in favour ofpaths.origincsrf.checkOriginis removed in favour ofcsrf.trustedOrigins
Added options
output.linkHeaderPreloaddetermines whether to useLinkHTTP headers to preload resources like.jsand.cssfiles rather than injecting<link>elements in the rendered HTML. This can cause issues when the headers grow too large, so SvelteKit 3 uses<link>elements by default instead.csrf.trustedOriginsallows you to specify external origins that are allowed to make form submissions.paths.originreplacesprerender.origin, and should reflect your app's public-facing origin if it can't reliably be derived from request headers (for example because it's behind a reverse proxy). It will be used for CSRF checks on form submissions and remote function calls. If usingadapter-node, this replaces theORIGINenvironment variable.
Changed options
version.pollIntervalnow defaults to one hour, meaning SvelteKit will periodically check for new deployments and setupdated.currenttotrueaccordingly. Previously, no polling occurred by default.
$lib is now #lib
The $lib alias is no longer generated automatically by SvelteKit. It is replaced by a #lib alias that you declare in the imports field of your package.json, leveraging Node's built-in subpath imports (which Vite and TypeScript resolve natively). Add this to your package.json...
{
"imports": {
"#lib": "./src/lib/index.js",
"#lib/*": "./src/lib/*"
}
}...and replace $lib with #lib across your codebase.
$app/environment (renamed)
The $app/environment module has been renamed to $app/env. It can now be imported inside your service worker, where previously if you needed to access version you would use the now-removed $service-worker module.
$app/forms
Forms with use:enhance that specify an action on a different page will navigate to that page upon submission, rather than staying on the current page. This ensures that the enhanced behaviour more closely matches the native, non-enhanced behaviour.
$app/manifest
A new $app/manifest module gives you access to metadata about your app. You can import this anywhere in your app, including in service workers for offline caching purposes, for which you would previously use the now-removed $service-worker module.
$app/navigation
Changes to shallow routing
For shallow routing, pushState/replaceState are deprecated in favor of goto:
// instead of this...
pushState('/foo', state);
replaceState('/bar', state);
// ...do this:
function goto(url: string | URL, opts?: import("@sveltejs/kit").GotoOptions): Promise<void>Allows you to navigate programmatically to a given route, with control over details such as whether scroll and focus are reset
(as they would be with a regular navigation) or preserved.
Returns a Promise that resolves when SvelteKit navigates (or fails to navigate, in which case the promise rejects) or the state change has been applied.
goto is intended for navigations to routes that belong to the app, and will reject if a route cannot be resolved.
For external URLs, use window.location = url to perform a full-page navigation instead of calling goto(url).
goto('/foo', { GotoOptions.shallow?: boolean | undefinedIf true, updates the URL and page.state without navigating.
shallow: true, GotoOptions.state?: App.PageState | undefinedAn optional object that will be available as page.state.
state });
function goto(url: string | URL, opts?: import("@sveltejs/kit").GotoOptions): Promise<void>Allows you to navigate programmatically to a given route, with control over details such as whether scroll and focus are reset
(as they would be with a regular navigation) or preserved.
Returns a Promise that resolves when SvelteKit navigates (or fails to navigate, in which case the promise rejects) or the state change has been applied.
goto is intended for navigations to routes that belong to the app, and will reject if a route cannot be resolved.
For external URLs, use window.location = url to perform a full-page navigation instead of calling goto(url).
goto('/bar', { GotoOptions.shallow?: boolean | undefinedIf true, updates the URL and page.state without navigating.
shallow: true, GotoOptions.replace?: boolean | undefinedIf true, replaces the current history entry rather than creating a new one.
replace: true, GotoOptions.state?: App.PageState | undefinedAn optional object that will be available as page.state.
state });A new persistState: true option will cause page.state to be reapplied following a page reload.
Shallow routing now triggers navigation hooks (beforeNavigate, onNavigate and afterNavigate). You can filter them out by checking the shallow property of the object passed to those navigation hooks.
invalidateAll is deprecated in favour of refreshAll
invalidateAll is deprecated in favour of refreshAll. The difference is that refreshAll does not reset page.state to an empty object, which is usually what you want when using shallow routing.
Additionally, calling invalidateAll() or invalidate(...) during an in-flight navigation no longer aborts that navigation.
goto options are updated
In addition to the new shallow option described above, various goto options have changed:
invalidateAllis nowrefreshAll, to mirror the above changekeepFocus: trueandnoScroll: truehave been combined asreset: falsereplaceStateis nowreplace
goto rejects for URLs that don't resolve to a route
goto(...) now rejects when called with a URL that does not resolve to a route within the app, matching the existing behaviour for external URLs. To navigate to an external URL, use window.location.href = url.
delta only exists for popstate navigations
The delta property on navigation events (beforeNavigate, onNavigate and afterNavigate) now only exists for popstate navigations (back/forward). It is undefined for all other navigation types.
preloadData can return an error result
preloadData(...) now returns { type: 'error', status, error } when the target page fails to load, instead of returning { type: 'loaded' } with a 200 status. The 'redirect' result now also includes the correct status. Add an error branch to any code that consumes the result:
const const result: ({
type: "loaded";
data: Record<string, any>;
} | {
type: "redirect";
location: string;
} | {
type: "error";
error: App.Error;
}) & {
status: number;
}
result = await function preloadData(href: string): Promise<({
type: "loaded";
data: Record<string, any>;
} | {
type: "redirect";
location: string;
} | {
type: "error";
error: App.Error;
}) & {
status: number;
}>
Programmatically preloads the given page, which means
- ensuring that the code for the page is loaded, and
- calling the page's load function with the appropriate options.
This is the same behaviour that SvelteKit triggers when the user taps or mouses over an <a> element with data-sveltekit-preload-data.
If the next navigation is to href, the values returned from load will be used, making navigation instantaneous.
Returns a Promise that resolves with the result of running the new route's load functions once the preload is complete.
preloadData(const url: "/somewhere"url);
if (const result: ({
type: "loaded";
data: Record<string, any>;
} | {
type: "redirect";
location: string;
} | {
type: "error";
error: App.Error;
}) & {
status: number;
}
result.type: "loaded" | "redirect" | "error"type === 'loaded') {
// ...
} else if (const result: ({
type: "redirect";
location: string;
} & {
status: number;
}) | ({
type: "error";
error: App.Error;
} & {
status: number;
})result.type: "redirect" | "error"type === 'error') {
// do something in case of an error
}$app/paths
base, assets, and resolveRoute removed
The deprecated base, assets, and resolveRoute exports have been removed from $app/paths. Use asset and resolve instead:
// instead of this...
const pathname = base + resolveRoute('/blog/[slug]', { slug });
const file = assets + '/foo.png';
// ...do this:
const const pathname: stringpathname = resolve<"/blog/[slug]">(route: "/blog/[slug]", params: Record<string, string>): ResolvedPathnameResolve a pathname by prefixing it with the base path, if any, or resolve a route ID by populating dynamic segments with parameters.
During server rendering, the base path is relative and depends on the page currently being rendered.
resolve('/blog/[slug]', { slug: stringslug });
const const file: stringfile = function asset(file: AssetPath): stringResolve the URL of an asset in your static directory, by prefixing it with config.paths.assets if configured, or otherwise by prefixing it with the base path.
During server rendering, the base path is relative and depends on the page currently being rendered.
asset('foo.png');The Pathname and Asset types have also been renamed to Path and AssetPath, and the leading / has been removed from those types — so asset('/foo.png') should now be asset('foo.png'), and pathnames passed to resolve no longer start with / (e.g. resolve('blog/hello-world')). Only route IDs start with / now.
Service workers can now import $app/paths
Previously, you needed to import base from the now-removed $service-worker module. You can now use asset(...) and resolve(...) from $app/paths.
$app/service-worker
A new $app/service-worker provides type-safe access to the service worker execution context in your src/service-worker/index.ts, provided you have a src/service-worker/tsconfig.json that extends $app/tsconfig/service-worker.
$app/state
page.url is now readonly
page.url is now typed as a ReadonlyURL with ReadonlyURLSearchParams, so mutating it — e.g. page.url.searchParams.set(...) or assigning to page.url.pathname — is now a type error. If you need a mutable URL, copy it first:
const const url: URLurl = new var URL: new (url: string | URL, base?: string | URL) => URLThe URL interface is used to parse, construct, normalize, and encode URLs. It works by providing properties which allow you to easily read and modify the components of a URL.
URL class is a global reference for import { URL } from 'url'
https://nodejs.org/api/url.html#the-whatwg-url-api
URL(page.url.href);
const url: URLurl.URL.searchParams: URLSearchParamsThe searchParams read-only property of the URL interface returns a URLSearchParams object allowing access to the GET decoded query arguments contained in the URL.
searchParams.URLSearchParams.set(name: string, value: string): voidThe set() method of the URLSearchParams interface sets the value associated with a given search parameter to the given value. If there were several matching values, this method deletes the others. If the search parameter doesn't exist, this method creates it.
set('q', 'svelte');updated updates automatically
The updated.current property becomes true when SvelteKit detects that a new version of the app has been deployed. Previously, this would only happen following a manual updated.check(), or after a failed navigation. In SvelteKit 3 it happens more frequently:
- on any navigation that results in data being fetched from the server
- on any remote function call
- when the window becomes visible or focused (e.g. when switching back from another tab)
- after a polling interval (which now defaults to one hour)
Note that if you use a feature like Vercel's skew protection, passive detection on navigation and remote functions may report false negatives, since the request will be handled by the earlier deployment. Polling and event-based checks will still work, since they bypass skew protection.
$app/stores (removed)
The $app/stores module (which exports the $page, $navigating, and $updated stores) has been removed. Use $app/state instead, which provides fine-grained Svelte 5 state, and remove the $ prefix when reading values (i.e. page rather than $page):
<script>
import { page } from '$app/state';
</script>
<p>current pathname: {page.url.pathname}</p>$app/tsconfig
Your project's tsconfig.json should now extend $app/tsconfig rather than ./.svelte-kit/tsconfig.json. It should also specify include and exclude arrays, as $app/tsconfig does not specify these:
{
"extends": "$app/tsconfig",
"include": ["src", "test", "*"],
"exclude": ["src/service-worker"]
}Some essential compilerOptions (isolatedModules and verbatimModuleSyntax) are included in $app/tsconfig, alongside various options that are strongly recommend but which can be overridden in your own config.
$app/tsconfig/service-worker
Your service worker needs to be part of a separate TypeScript project, otherwise the types for things like fetch events will be incorrect. To do this, exclude the service worker from your project's root tsconfig.json, and add a src/service-worker/tsconfig.json that extends $app/tsconfig/service-worker:
{
"extends": "$app/tsconfig/service-worker"
}$env/... (deprecated)
The various $env/... modules have been deprecated in favour of $app/env/private and $app/env/public — see Environment variables for more details.
$service-worker (removed)
The $service-worker module has been removed. Import version from $app/env, assets, immutable and prerendered from $app/manifest, and resolved from $app/paths instead.
@sveltejs/kit
error, isHttpError, redirect, and isRedirect refer to public types
error, isHttpError, redirect, and isRedirect now refer to the public types rather than the internal classes. If you were importing the internal HttpError / Redirect classes from @sveltejs/kit/internal, or doing instanceof checks against them, use isHttpError/isRedirect from @sveltejs/kit instead.
json and text are deprecated
The json(...) and text(...) helpers for generating responses are deprecated. Use Response.json(...) and new Response(text) instead.
@sveltejs/kit/hooks
The defineEnvVars function has moved from @sveltejs/kit/hooks to @sveltejs/kit/env.
@sveltejs/kit/node
The getRequest and setResponse helpers are now synchronous and no longer return Promises. Remove await from calls in custom Node servers or adapters.
@sveltejs/kit/node/polyfills (removed)
The @sveltejs/kit/node/polyfills module (and the Node global shims in adapter-node and adapter-netlify) applied to Node versions that are no longer supported. Remove any import '@sveltejs/kit/node/polyfills' statements from your custom server code.
Security
csrf.checkOrigin replaced by csrf.trustedOrigins
The deprecated csrf.checkOrigin option has been removed. CSRF protection is always on; instead of disabling it with checkOrigin: false, allow trusted cross-origin hosts with csrf.trustedOrigins.
export default function defineConfig(config: UserConfig): UserConfig (+5 overloads)Type helper to make it easier to use vite.config.ts
accepts a direct
{@link
UserConfig
}
object, or a function that returns it.
The function receives a
{@link
ConfigEnv
}
object.
defineConfig({
UserConfig.plugins?: PluginOption[] | undefinedArray of vite plugins to use.
plugins: [
function sveltekit(config?: KitConfig & Omit<Options, "onwarn"> & Pick<SvelteConfig, "vitePlugin">): Promise<Plugin[]>Returns the SvelteKit Vite plugins.
Any options that don't belong to SvelteKit are passed through to vite-plugin-svelte.
Since version 3.0.0 you must pass configuration directly.
Since version 2.62.0 you can pass configuration directly, in which case svelte.config.js is ignored.
sveltekit({
KitConfig.csrf?: {
checkOrigin?: boolean;
trustedOrigins?: string[];
} | undefined
Protection against cross-site request forgery (CSRF) attacks.
csrf: {
checkOrigin: false
trustedOrigins?: string[] | undefinedAn array of origins that are allowed to make cross-origin form submissions to your app.
Each origin should be a complete origin including protocol (e.g., https://payment-gateway.com).
This is useful for allowing trusted third-party services like payment gateways or authentication providers to submit forms to your app.
If the array contains '*', all origins will be trusted. This is generally not recommended!
Only add origins you completely trust, as this bypasses CSRF protection for those origins.
CSRF checks only apply in production, not in local development.
trustedOrigins: ['https://trusted-site.com']
}
})
]
});Cross-origin form submissions require a Content-Type header
Cross-origin mutative requests that omit a Content-Type header are now rejected as CSRF. Ensure cross-origin form submissions include a Content-Type header, or add the origin to csrf.trustedOrigins.
CORS for static assets in development is handled by Vite
SvelteKit no longer sets access-control-allow-origin: * on every static asset request in development. CORS is now delegated to Vite's built-in middleware. If you rely on cross-origin access to static assets in dev, configure it in your Vite config:
export default function defineConfig(config: UserConfig): UserConfig (+5 overloads)Type helper to make it easier to use vite.config.ts
accepts a direct
{@link
UserConfig
}
object, or a function that returns it.
The function receives a
{@link
ConfigEnv
}
object.
defineConfig({
UserConfig.server?: ServerOptions$1 | undefinedServer specific options, e.g. host, port, https...
server: {
CommonServerOptions.cors?: boolean | CorsOptions | undefinedConfigure CORS for the dev server.
Uses https://github.com/expressjs/cors.
When enabling this option, we recommend setting a specific value
rather than true to avoid exposing the source code to untrusted origins.
Set to true to allow all methods from any origin, or configure separately
using an object.
cors: { CorsOptions.origin?: CorsOrigin | ((origin: string | undefined, cb: (err: Error, origins: CorsOrigin) => void) => void) | undefinedConfigures the Access-Control-Allow-Origin CORS header.
We recommend setting a specific value rather than
true to avoid exposing the source code to untrusted origins.
origin: '*' }
}
});Cookies
Updated to cookie v2
SvelteKit now uses cookie v2, which involves certain changes:
- cookie names can only contain ASCII characters. Non-ASCII characters (including Latin-1 Supplement characters like
á) are rejected - the
CookieSerializeOptionstype has been renamed toSerializeOptions - the
CookieParseOptionstype has been renamed toParseOptions
Paths default to '/'
When setting a cookie without an explicit path (which was previously forbidden), the path defaults to '/' rather than the current request path, meaning the cookie applies to the entire site. This matches what most developers expect. You can pass an explicit path if necessary:
const cookies: Cookiescookies.Cookies.set: (name: string, value: string, opts: SerializeOptions) => voidSets a cookie. This will add a set-cookie header to the response, but also make the cookie available via cookies.get or cookies.getAll during the current request.
The httpOnly is true by default, as is secure, except during development, when it defaults to false. These must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP.
The path option is '/' by default. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children.
set(const name: stringname, const value: stringvalue, { path?: string | undefinedSpecifies the value for the Path Set-Cookie attribute.
When no path is set, the path is considered the "default path".
path: '/some/path' });Error handling
App.Error always includes status
An App.Error object, such as the error prop of an +error.svelte component, has a status property reflecting the HTTP status of the error that caused it (e.g. 404 for a Not Found error, or 500 for a generic internal error), in addition to message and whatever properties you define in your app.d.ts file.
error(...) arguments changed
Previously, the second argument to error(...) could be either the message as a string, or an object containing message alongside any additional properties defined in app.d.ts (such as a tracking code).
Now, the second argument must always be a string. If there are additional properties, they must be passed as a third argument.
handleValidationError is removed
Validation errors are now passed to handleError, with kind: 'validation'.
handleError receives all errors
In SvelteKit 2, handleError was not called in the case of expected errors, which is to say those created with the error(...) helper. In SvelteKit 3, all errors are passed to handleError. See the docs for more information.
handleError can influence the status code
If you need to control the HTTP status code used to render a page in the case of an error, you can do so by returning a status property from handleError alongside any other required properties of App.Error.
Rendering errors are now handled
Errors thrown during rendering are now always routed through handleError and then passed to the nearest error boundary. Error boundaries are automatically created for each of your +error.svelte components.
If you have an async handleError hook in hooks.client.ts, enable compilerOptions.experimental.async in the sveltekit(...) plugin options of your Vite config so it can be awaited during rendering.
Form action responses use the fail status code
Enhanced form action responses now use the HTTP status code passed to fail(...) instead of always returning 200. If you inspect status codes on enhanced form submissions (for example in a use:enhance callback or in tests), they now reflect the value passed to fail.
Sourcemaps are applied
Sourcemaps are generated by default, and applied to stack traces when errors occur. For this to work in production (as opposed to vite preview), adapters need to avoid any destructive changes (such as rebundling without generating additional — and correct — sourcemaps). For first-party adapters this is a work in progress.
Params
Param matchers are no longer files inside the src/params directory. Declare all matchers in a single src/params.ts (or src/params.js) file using the defineParams helper. A matcher can be a function that returns a parsed value (or undefined, if the param does not match), or a Standard Schema.
import { function defineParams<T extends Record<string, ParamDefinition>>(definitions: T): DefinedParams<T>Define parameter matchers for your app.
defineParams } from '@sveltejs/kit';
import * as import vv from 'valibot';
export const const params: DefinedParams<{
integer: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.ToNumberAction<string, undefined>]>;
fruit: (param: string) => "apple" | "orange" | undefined;
}>
params = defineParams<{
integer: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.ToNumberAction<string, undefined>]>;
fruit: (param: string) => "apple" | "orange" | undefined;
}>(definitions: {
integer: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.ToNumberAction<string, undefined>]>;
fruit: (param: string) => "apple" | "orange" | undefined;
}): DefinedParams<{
integer: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.ToNumberAction<string, undefined>]>;
fruit: (param: string) => "apple" | "orange" | undefined;
}>
Define parameter matchers for your app.
defineParams({
// schema variant
integer: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.ToNumberAction<string, undefined>]>integer: import vv.pipe<v.StringSchema<undefined>, v.ToNumberAction<string, undefined>>(schema: v.StringSchema<undefined>, item1: v.ToNumberAction<string, undefined> | v.PipeAction<string, number, v.ToNumberIssue<string>>): v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.ToNumberAction<string, undefined>]> (+20 overloads)
export pipe
Adds a pipeline to a schema, that can validate and transform its input.
pipe(import vv.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), import vv.toNumber<string>(): v.ToNumberAction<string, undefined> (+1 overload)
export toNumber
Creates a to number transformation action.
toNumber()),
// function variant
fruit: (param: string) => "apple" | "orange" | undefinedfruit: (param: stringparam) => {
if (param: stringparam === 'apple' || param: stringparam === 'orange') {
return param: "apple" | "orange"param;
}
}
});Observability
Server-side instrumentation now happens automatically if a src/instrumentation.server.js file exists.
To opt into OpenTelemetry tracing, add tracing.server configuration:
export default function defineConfig(config: UserConfig): UserConfig (+5 overloads)Type helper to make it easier to use vite.config.ts
accepts a direct
{@link
UserConfig
}
object, or a function that returns it.
The function receives a
{@link
ConfigEnv
}
object.
defineConfig({
UserConfig.plugins?: PluginOption[] | undefinedArray of vite plugins to use.
plugins: [
function sveltekit(config?: KitConfig & Omit<Options, "onwarn"> & Pick<SvelteConfig, "vitePlugin">): Promise<Plugin[]>Returns the SvelteKit Vite plugins.
Any options that don't belong to SvelteKit are passed through to vite-plugin-svelte.
Since version 3.0.0 you must pass configuration directly.
Since version 2.62.0 you can pass configuration directly, in which case svelte.config.js is ignored.
sveltekit({
KitConfig.tracing?: {
server?: boolean;
} | undefinedOptions for enabling OpenTelemetry tracing for SvelteKit operations.
server?: boolean | undefinedEnables server-side OpenTelemetry span emission for SvelteKit operations including the handle hook, load functions, form actions, and remote functions. Tracing — and more significantly, observability instrumentation — can have a nontrivial overhead, so consider whether you really need it, or if it might be more appropriate to turn it on in development and preview environments only.
Adapters
All first-party adapters now require SvelteKit 3, alongside these adapter-specific changes:
adapter-cloudflare
- minimum
wrangleris now^4.67.0 @cloudflare/workers-typesupgradedplatform.contextremoved in favour ofplatform.ctx
adapter-node
- bundling now happens with
rolldown - the
ORIGINenvironment variable is removed (setpaths.originin your Vite config instead)
adapter-netlify
- output now conforms to the stable Netlify Frameworks API
- deploying/previewing with the Netlify CLI requires
v17.31.0or later (npm i -g netlify-cli@latest) - edge function build target is
es2022
adapter-vercel
- the
edgeruntime is no longer supported
Adapter API changes
For adapter authors, there are some additional changes:
- adapters can augment the Vite config with additional plugins
builder.createEntrieshas been removed — usebuilder.writeClient,builder.writeServerandbuilder.writePrerendereddirectlybuilder.compressreturns a list of compressed filesbuilder.mkdirpandbuilder.rimrafare deprecated in favour ofnode:fsmethods
Responses
204 responses return no content
Returning a 204 (or any empty 2xx) response from a +server.js handler now results in a response with no body, per the HTTP spec, rather than a SvelteKit envelope. Code that consumed the body of such responses needs to handle the empty body.
resolve always returns a Promise
The resolve function passed to handle is now typed to always return a Promise<Response> rather than MaybePromise<Response>.
Server-only modules
Files
Server-only modules are now designated by a filename with a server segment, rather than a .server. infix — in other words, stuff.server.ts, stuff.server.test.ts and server.ts are all treated as server-only modules, whereas server.ts previously was not.
Directories
Previously, any module inside src/lib/server was treated as server-only. This treatment now applies to any server directory in the project with the exception of src/routes and your static directory.
Remote functions
Remove functions are still considered experimental — opt in via the experimental.remoteFunctions flag alongside compilerOptions.experimental.async:
export default function defineConfig(config: UserConfig): UserConfig (+5 overloads)Type helper to make it easier to use vite.config.ts
accepts a direct
{@link
UserConfig
}
object, or a function that returns it.
The function receives a
{@link
ConfigEnv
}
object.
defineConfig({
UserConfig.plugins?: PluginOption[] | undefinedArray of vite plugins to use.
plugins: [
function sveltekit(config?: KitConfig & Omit<Options, "onwarn"> & Pick<SvelteConfig, "vitePlugin">): Promise<Plugin[]>Returns the SvelteKit Vite plugins.
Any options that don't belong to SvelteKit are passed through to vite-plugin-svelte.
Since version 3.0.0 you must pass configuration directly.
Since version 2.62.0 you can pass configuration directly, in which case svelte.config.js is ignored.
sveltekit({
compilerOptions?: Omit<CompileOptions, "filename" | "format" | "generate"> | undefinedThe options to be passed to the Svelte compiler. A few options are set by default,
including dev and css. However, some options are non-configurable, like
filename, format, generate, and cssHash (in dev).
compilerOptions: {
experimental?: {
async?: boolean;
} | undefined
Experimental options
experimental: {
async?: boolean | undefinedAllow await keyword in deriveds, template expressions, and the top level of components
async: true
}
},
KitConfig.experimental?: ({
remoteFunctions?: boolean;
forkPreloads?: boolean;
} & ExperimentalOptions) | undefined
Experimental features. Here be dragons. These are not subject to semantic versioning, so breaking changes or removal can happen in any release.
These options are considered experimental and breaking changes to them can occur in any release
experimental: {
remoteFunctions?: boolean | undefinedWhether to enable the experimental remote functions feature. This feature is not yet stable and may be changed or removed at any time.
remoteFunctions: true
}
})
]
});Remote module filenames
As with server-only modules, a remote segment in a filename designates a remote module — stuff.remote.ts, stuff.remote.test.ts and remote.ts are all remote modules.
If experimental.remoteFunctions is not enabled, the existence of these files will cause an error.
event.url, event.params, and event.route cannot be accessed inside queries
Accessing event.url, event.params, or event.route inside a remote query function now throws an error. These properties are not meaningful in the context of a remote function (which can be called from anywhere). Pass any values you need explicitly as arguments to the function.
Errors are typed as App.Error | undefined
The error property on remote function resources (queries, live queries, forms, prerender functions) is now typed as App.Error | undefined rather than any, as the error is always transformed by handleError.
Form submissions require field.as(...)
A form control must use attributes from a field associated with the current form object:
<input {...myform.fields.message.as('text')}>Manually specifying a name (<input name="message">) will cause the submission to be rejected.
Miscellaneous
Links to the current page cause a refresh
If the user clicks a link that points to the current location, SvelteKit will refreshAll() instead of doing nothing.
data-sveltekit-* uses false instead of 'off'
The 'off' value for data-sveltekit-* link attributes has been removed in favour of false.
<a href="..." data-sveltekit-preload-data="off">
<a href="..." data-sveltekit-preload-data="false">External redirects must be opted into
To redirect to an external URL you must now pass an external option — either true to allow any external URL (except javascript: URLs, which remain blocked), or an array of allowed origins (which can include javascript: URLs).
function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL, options?: {
external?: boolean | string[];
}): never
Redirect a request. When called during request handling, SvelteKit will return a redirect response.
Make sure you're not catching the thrown redirect, which would prevent SvelteKit from handling it.
Most common status codes:
303 See Other: redirect as a GET request (often used after a form POST request)
307 Temporary Redirect: redirect will keep the request method
308 Permanent Redirect: redirect will keep the request method, SEO will be transferred to the new page
redirect(307, 'https://example.com', { external?: boolean | string[] | undefinedexternal: true });Universal config takes precedence over server config
Route config exported from a universal +page.js or +layout.js now takes precedence over config exported from the corresponding +page.server.js or +layout.server.js, matching how other page options are resolved. If you export config from both, move the canonical export to the universal file or consolidate them.
Service worker registrations use type: 'module'
Module service workers are now widely supported. As such, SvelteKit will bundle and register your service worker as a module, rather than as a script.
Edit this page on GitHub llms.txt