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:
npxsvmigratesveltekit-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.57.1
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:
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.
As well as SvelteKit, the plugin options are used by other tooling that integrates with Svelte such as editor extensions.
Any options that don't belong to SvelteKit are passed through to vite-plugin-svelte, so you can set options like inspector here too. The experimental namespace is shared — SvelteKit reads its own flags and forwards the rest.
[!LEGACY]
Prior to SvelteKit 3, config lived in a svelte.config.js file, which is no longer supported. The ability to configure SvelteKit via vite.config.js was added in version 2.62.
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.
As well as SvelteKit, the plugin options are used by other tooling that integrates with Svelte such as editor extensions.
Any options that don't belong to SvelteKit are passed through to vite-plugin-svelte, so you can set options like inspector here too. The experimental namespace is shared — SvelteKit reads its own flags and forwards the rest.
[!LEGACY]
Prior to SvelteKit 3, config lived in a svelte.config.js file, which is no longer supported. The ability to configure SvelteKit via vite.config.js was added in version 2.62.
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).
experimental.handleRenderingErrors is no longer required (details)
experimental.instrumentation is no longer required (details)
experimental.tracing is now a top level tracing option (details)
vitePlugin is removed — pass vite-plugin-svelte options like inspector directly to the plugin instead
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 takes precedence over 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...
...and replace $lib with #lib across your codebase. Note that you will also have to add the module extensions (e.g. .js or .ts) to these imports.
import { foo } from '$lib/foo';import{fooimportfoo}from'#lib/foo.js';
$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:
goto(alias)goto(url:string|URL,opts?:GotoOptions):Promise<void>importgoto
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).
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.
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).
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.
An optional object that will be available as page.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:
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.
href
Page to preload
(urlconsturl:"/somewhere");if(resultconstresult:({type:"loaded";data:Record<string,any>;}|{type:"redirect";location:string;}|{type:"error";error:App.Error;})&{status:number;}.type(property)type:"loaded"|"redirect"|"error"==='loaded'){ // ...
}elseif(resultconstresult:({type:"redirect";location:string;}&{status:number;})|({type:"error";error:App.Error;}&{status:number;}).type(property)type:"redirect"|"error"==='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:
constpathnameconstpathname:string=resolve(alias)resolve<"/blog/[slug]">(route:"/blog/[slug]",params:Record<string,string>):ResolvedPathnameimportresolve
Resolve a pathname by prefixing it with the base path, if any, or resolve a route ID by populating dynamic segments with parameters.
In hash routing mode, the returned URL starts with #.
During server rendering, the base path is relative and depends on the page currently being rendered.
import{resolve}from'$app/paths';// using a pathnameconstresolved=resolve(`blog/hello-world`);// using a route ID plus parametersconstresolved=resolve('/blog/[slug]',{slug:'hello-world'});
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.
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.
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:
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.
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.
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.
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):
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:
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.
Types like EnvVarConfig, used with defineEnvVars hook, now live in @sveltejs/kit/env.
Hooks-related types moved to @sveltejs/kit/hooks
Types like Handle, which defines the type of your handle hook, now live in @sveltejs/kit/hooks.
Remote function types moved to $app/server
Types describing remote functions, such as RemoteQuery, RemoteForm and RemoteCommand, now live in $app/server alongside the functions themselves.
@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.
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.
As well as SvelteKit, the plugin options are used by other tooling that integrates with Svelte such as editor extensions.
Any options that don't belong to SvelteKit are passed through to vite-plugin-svelte, so you can set options like inspector here too. The experimental namespace is shared — SvelteKit reads its own flags and forwards the rest.
[!LEGACY]
Prior to SvelteKit 3, config lived in a svelte.config.js file, which is no longer supported. The ability to configure SvelteKit via vite.config.js was added in version 2.62.
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!
[!NOTE] Only add origins you completely trust, as this bypasses CSRF protection for those origins.
CSRF checks only apply in production, not in local development.
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:
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.
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.
:'*'}}});
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 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:
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.
name
the name of the cookie
value
the cookie value
opts
the options passed to cookie.stringifySetCookie with the SvelteKit defaults described above. See documentation here
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'. The generic { status, message }error object is safe to return, while the validation issues are provided separately for logging or custom handling.
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.
()), // function variant
fruit(property)fruit:(param:string)=>"apple"|"orange"|undefined:(param(parameter)param:string)=>{if(param(parameter)param:string==='apple'||param(parameter)param:string==='orange'){returnparam(parameter)param:"apple"|"orange";}}});
Observability
Server-side instrumentation now happens automatically if a src/instrumentation.server.js file exists.
To opt into OpenTelemetry tracing, add tracing.server configuration:
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.
As well as SvelteKit, the plugin options are used by other tooling that integrates with Svelte such as editor extensions.
Any options that don't belong to SvelteKit are passed through to vite-plugin-svelte, so you can set options like inspector here too. The experimental namespace is shared — SvelteKit reads its own flags and forwards the rest.
[!LEGACY]
Prior to SvelteKit 3, config lived in a svelte.config.js file, which is no longer supported. The ability to configure SvelteKit via vite.config.js was added in version 2.62.
Options for enabling OpenTelemetry tracing for SvelteKit operations.
{ server: false }
:{server(property)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.
false
:true}})]});
Adapters
All first-party adapters now require SvelteKit 3, alongside these adapter-specific changes:
adapter-cloudflare
Cloudflare-specific APIs are no longer available on platform. Instead, find them where you would expect on a Cloudflare worker:
env, ctx.waitUntil, and other ctx properties should be imported from cloudflare:workers:
The match() method of the Cache interface returns a Promise that resolves to the Response associated with the first matching request in the Cache object. If no match is found, the Promise resolves to undefined.
static assets are served from a list recorded at build time; files added to the output directory afterwards are not served, and replaced ones keep their old size and ETag (use environment variables for runtime configuration)
ETags for static assets are content hashes, and Last-Modified is no longer sent
only GET and HEAD requests are served static assets; other methods result in a 405 response
deploying/previewing with the Netlify CLI requires v17.31.0 or later (npm i -g netlify-cli@latest)
edge function build target is es2022
the publish directory is now an adapter option rather than being read from the netlify.toml file
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.config.kit no longer exists — the configuration now lives at the top level
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
builder.generateManifest has been removed — use builder.generateServerInstance to replace it, and builder.manifest to access the manifest
the Server class exported from the server output is deprecated — use the server object written by builder.generateServerInstance
Server instrumentation
Adapters that use builder.instrument must now generate an environment initializer before any
bundling step, include the returned module as an entrypoint in that step, and pass its final path to
builder.instrument:
Generate an initializer that populates $env/dynamic/private before server instrumentation
runs. Include the returned module in any subsequent bundling or tracing step.
options
an object containing the following properties:
options.outputDirectory
the directory in which to create the initializer.
options.environment
the contents of a module whose default export contains the platform's environment variables. If omitted, process.env is used.
options.serverDirectory
the directory containing the server build output. Defaults to getServerDirectory().
the filesystem path to the generated initializer.
3.0.0
({outputDirectory(property)outputDirectory:string:temporary_directoryconsttemporary_directory:string});// Include `initializer` in any bundling or file tracing here
builderconstbuilder:Builder.instrument(property)Builder.instrument:(args:{entrypoint:string;instrumentation:string;start?:string;initializer:string;module?:{exports:string[];}|{generateText:(args:{instrumentation:string;start:string;initializer:string;})=>string;};})=>void
Instrument entrypoint with instrumentation.
Renames entrypoint to start and creates a new module at
entrypoint which imports instrumentation and then dynamically imports start. This allows
the module hooks necessary for instrumentation libraries to be loaded prior to any application code.
initializer is a module generated by createInstrumentationInitializer. It must be included
in any bundling or tracing step before calling this method.
Caveats:
"Live exports" will not work. If your adapter uses live exports, your users will need to manually import the server instrumentation on startup.
If tla is false, OTEL auto-instrumentation may not work properly. Use it if your environment supports it.
Use hasServerInstrumentationFile to check if the user has a server instrumentation file; if they don't, you shouldn't do this.
options
an object containing the following properties:
options.entrypoint
the path to the entrypoint to trace.
options.instrumentation
the path to the instrumentation file.
options.start
the name of the start file. This is what entrypoint will be renamed to.
options.initializer
the filesystem path to the bundled or copied instrumentation initializer.
options.module
configuration for the resulting entrypoint module.
options.module.generateText
a function that receives the relative paths to the initializer, instrumentation and start files, and generates the text of the module to be traced. It must import initializer before instrumentation, and dynamically import start after instrumentation has run. If not provided, the default implementation will be used, which uses top-level await.
For runtimes that do not expose environment variables through process.env, pass the contents of
a module whose default export is the platform environment:
Generate an initializer that populates $env/dynamic/private before server instrumentation
runs. Include the returned module in any subsequent bundling or tracing step.
options
an object containing the following properties:
options.outputDirectory
the directory in which to create the initializer.
options.environment
the contents of a module whose default export contains the platform's environment variables. If omitted, process.env is used.
options.serverDirectory
the directory containing the server build output. Defaults to getServerDirectory().
the filesystem path to the generated initializer.
3.0.0
({outputDirectory(property)outputDirectory:string:temporary_directoryconsttemporary_directory:string,environment(property)environment?:string|undefined:`import { env } from 'cloudflare:workers';\nexport default env;`});
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 anyserver directory in the project with the exception of src/routes and your static directory.
Remote functions
Remote functions are still considered experimental — opt in via the experimental.remoteFunctions flag alongside compilerOptions.experimental.async:
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.
As well as SvelteKit, the plugin options are used by other tooling that integrates with Svelte such as editor extensions.
Any options that don't belong to SvelteKit are passed through to vite-plugin-svelte, so you can set options like inspector here too. The experimental namespace is shared — SvelteKit reads its own flags and forwards the rest.
[!LEGACY]
Prior to SvelteKit 3, config lived in a svelte.config.js file, which is no longer supported. The ability to configure SvelteKit via vite.config.js was added in version 2.62.
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).
Whether to enable the experimental remote functions feature. This feature is not yet stable and may be changed or removed at any time.
false
: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.
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).
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
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.
{import('./public.js').Redirect} This error instructs SvelteKit to redirect to the specified location.
{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.
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.