Skip to main content

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-3

We 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 rolldown v1)
  • @sveltejs/vite-plugin-svelte v7

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:

vite.config
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[] | undefined

Array 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"> | undefined

The 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

@since
5.36
experimental
: { async?: boolean | undefined

Allow await keyword in deriveds, template expressions, and the top level of components

@since
5.36
async
: true }},
KitConfig.adapter?: Adapter | undefined

Your adapter is run when executing vite build. It determines how the output is converted for different platforms.

@default
undefined
adapter
: 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.handleRenderingErrors is no longer required (details)
  • experimental.instrumentation is no longer required (details)
  • experimental.tracing is now a top level tracing option (details)
  • preloadStrategy is removed — modulepreload is now supported everywhere and so is always used
  • prerender.origin is removed in favour of paths.origin
  • csrf.checkOrigin is removed in favour of csrf.trustedOrigins

Added options

  • output.linkHeaderPreload determines whether to use Link HTTP headers to preload resources like .js and .css files 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.trustedOrigins allows you to specify external origins that are allowed to make form submissions.
  • paths.origin replaces prerender.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 using adapter-node, this replaces the ORIGIN environment variable.

Changed options

  • version.pollInterval now defaults to one hour, meaning SvelteKit will periodically check for new deployments and set updated.current to true accordingly. 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...

package
{
	"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).

@param
url Where to navigate to. Note that if you've set config.paths.base and the URL is root-relative, you need to prepend the base path if you want to navigate within the app.
@param
opts Options related to the navigation
goto
('/foo', { GotoOptions.shallow?: boolean | undefined

If true, updates the URL and page.state without navigating.

@default
false
shallow
: true, GotoOptions.state?: App.PageState | undefined

An 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).

@param
url Where to navigate to. Note that if you've set config.paths.base and the URL is root-relative, you need to prepend the base path if you want to navigate within the app.
@param
opts Options related to the navigation
goto
('/bar', { GotoOptions.shallow?: boolean | undefined

If true, updates the URL and page.state without navigating.

@default
false
shallow
: true, GotoOptions.replace?: boolean | undefined

If true, replaces the current history entry rather than creating a new one.

@default
false
replace
: true, GotoOptions.state?: App.PageState | undefined

An 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:

  • invalidateAll is now refreshAll, to mirror the above change
  • keepFocus: true and noScroll: true have been combined as reset: false
  • replaceState is now replace

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

  1. ensuring that the code for the page is loaded, and
  2. 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.

@param
href Page to preload
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>): ResolvedPathname

Resolve 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.

@example
import { resolve } from '$app/paths';

// using a pathname
const resolved = resolve(`blog/hello-world`);

// using a route ID plus parameters
const resolved = resolve('/blog/[slug]', {
	slug: 'hello-world'
});
@since
2.26
resolve
('/blog/[slug]', { slug: stringslug });
const const file: stringfile = function asset(file: AssetPath): string

Resolve 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.

@example
<script>
	import { asset } from '$app/paths';
</script>

<img alt="a potato" src={asset('potato.jpg')} />
@since
2.26
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) => URL

The 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.

MDN Reference

URL class is a global reference for import { URL } from 'url' https://nodejs.org/api/url.html#the-whatwg-url-api

@since
v10.0.0
URL
(page.url.href);
const url: URLurl.URL.searchParams: URLSearchParams

The searchParams read-only property of the URL interface returns a URLSearchParams object allowing access to the GET decoded query arguments contained in the URL.

MDN Reference

searchParams
.URLSearchParams.set(name: string, value: string): void

The 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.

MDN Reference

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:

tsconfig
{
	"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:

src/service-worker/tsconfig
{
	"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.

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.plugins?: PluginOption[] | undefined

Array 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[] | undefined

An 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.

@default
[]
@example
['https://checkout.stripe.com', 'https://accounts.google.com']
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:

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 | undefined

Server specific options, e.g. host, port, https...

server
: {
CommonServerOptions.cors?: boolean | CorsOptions | undefined

Configure 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.

@default
false
cors
: { CorsOptions.origin?: CorsOrigin | ((origin: string | undefined, cb: (err: Error, origins: CorsOrigin) => void) => void) | undefined

Configures 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

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 CookieSerializeOptions type has been renamed to SerializeOptions
  • the CookieParseOptions type has been renamed to ParseOptions

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) => void

Sets 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.

@param
name the name of the cookie
@param
value the cookie value
@param
opts the options passed to cookie.stringifySetCookie with the SvelteKit defaults described above. See documentation here
set
(const name: stringname, const value: stringvalue, { path?: string | undefined

Specifies 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.

src/params
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.

@param
schema The root schema.
@param
item1 The first pipe item.
@returns
A schema with a pipeline.
pipe
(import vv.
function string(): v.StringSchema<undefined> (+1 overload)
export string

Creates a string schema.

@returns
A string schema.
string
(), import vv.
toNumber<string>(): v.ToNumberAction<string, undefined> (+1 overload)
export toNumber

Creates a to number transformation action.

@returns
A to number action.
@beta
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:

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.plugins?: PluginOption[] | undefined

Array 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;
} | undefined

Options for enabling OpenTelemetry tracing for SvelteKit operations.

@default
{ server: false }
tracing
: { server?: boolean | undefined

Enables 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.

@default
false
server
: true }
}) ] });

Adapters

All first-party adapters now require SvelteKit 3, alongside these adapter-specific changes:

adapter-cloudflare

  • minimum wrangler is now ^4.67.0
  • @cloudflare/workers-types upgraded
  • platform.context removed in favour of platform.ctx

adapter-node

  • bundling now happens with rolldown
  • the ORIGIN environment variable is removed (set paths.origin in your Vite config instead)

adapter-netlify

  • output now conforms to the stable Netlify Frameworks API
  • deploying/previewing with the Netlify CLI requires v17.31.0 or later (npm i -g netlify-cli@latest)
  • edge function build target is es2022

adapter-vercel

  • the edge runtime 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.createEntries has been removed — use builder.writeClient, builder.writeServer and builder.writePrerendered directly
  • builder.compress returns a list of compressed files
  • builder.mkdirp and builder.rimraf are deprecated in favour of node:fs methods

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:

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.plugins?: PluginOption[] | undefined

Array 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"> | undefined

The 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

@since
5.36
experimental
: {
async?: boolean | undefined

Allow await keyword in deriveds, template expressions, and the top level of components

@since
5.36
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 | undefined

Whether to enable the experimental remote functions feature. This feature is not yet stable and may be changed or removed at any time.

@default
false
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

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

See all redirect status codes

@param
status The HTTP status code. Must be in the range 300-308.
@param
location The location to redirect to.
@param
options To redirect to an external URL, you must pass { external: true } to allow any external URL except javascript: URLs, or { external: [...] } with an allowlist of permitted origins.
@throws
import('./public.js').Redirect This error instructs SvelteKit to redirect to the specified location.
@throws
Error If the provided status is invalid, the location cannot be used as a header value, or the location is an external URL without permission.
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