SvelteKit 3: what changes and how to migrate your app
SvelteKit 3 moves config into Vite, swaps $lib for #lib and reworks env vars and errors. What changes for a SvelteKit 2 app, in the order to migrate.
Source-based. Written from the documents, reporting and reviews linked in the text. Nothing here was tested hands-on by The Ruling Desk. How we work

SvelteKit 3 is out, and if you run a SvelteKit 2 app, the upgrade touches your config file, your imports, your environment variables and your error handling. The Svelte team shipped it on October 1, 2026, with a migration command that converts as much code as it can and leaves a to-do list for the rest. This guide walks through what actually changes and the order to tackle it in, built from the official migration guide. We describe the documented steps; we didn't run them on a production app.
Key takeaways
- SvelteKit 3.0.0 was published on October 1, 2026. Config moves from
svelte.config.jsintovite.config.ts, and the$libalias becomes#lib. - The command
npx sv migrate sveltekit-3automates nine documented tasks, but swapping$libfor#libis a manual step, according to the Svelte team. - Environment variables now live in one
src/env.tsfile, and every error, including ones you throw witherror(), now passes throughhandleError. - You need Node 22.17, TypeScript 6 and Vite 8.0.12 or newer before you start.
- Remote functions are still experimental in 3.0 and need the Async Svelte flag.
What SvelteKit 3 changes
The release announcement lists the headline changes: configuration now lives in vite.config.ts instead of svelte.config.js, $lib becomes #lib using standard subpath imports, environment variables get a new explicit system, service workers need less boilerplate, and error handling is reworked across the board. The 3.0.0 release notes on GitHub are dated October 1, 2026, at 17:21 UTC.
The reasons are mostly about removing glue code. In the release candidate post from August 13, the team explained that putting config in the Vite file gives the Vite plugin your settings immediately, without an asynchronous lookup. The #lib switch leans on Node's subpath imports, which Vite and TypeScript already understand, so SvelteKit no longer has to keep its own alias in sync across tools.
Several older APIs are gone or deprecated. The migration guide says $app/stores is removed in favor of $app/state, base, assets and resolveRoute are removed from $app/paths, the $service-worker module is deleted, and $app/environment is renamed to $app/env.
What is still experimental
Remote functions, SvelteKit's type-safe way to call server code from the client, are not finished. The announcement calls them the team's top priority and says they still require the experimental Async Svelte feature. The migration guide says you enable them with experimental.remoteFunctions: true plus compilerOptions.experimental.async: true. If your app doesn't use them, this changes nothing for you today.
Step 1: Check the new minimum versions
Before running anything, the migration guide lists these minimums:
| Dependency | Minimum for SvelteKit 3 |
|---|---|
| Node | 22.17 |
| TypeScript | 6 |
| Svelte | 5.57.1 (the 3.0.0 changelog says 5.56.4) |
| Vite | 8.0.12 |
@sveltejs/vite-plugin-svelte | 7 |
Upgrade Node and TypeScript in your CI and hosting first, since those live outside your repo. Then commit or stash your work: the sv migrate documentation says the tool prompts before changing files when your git working tree is dirty, and a clean tree makes its diff easy to review.
Step 2: Run the SvelteKit 3 migration
The announcement gives the one-shot command, npx sv migrate sveltekit-3 --tasks all --confirm, which applies every task without prompting and writes a to-do list for what it can't fix. Run npx sv migrate sveltekit-3 without flags if you'd rather pick tasks one by one. Per the sv docs, the nine tasks are:
- package-json: updates package versions.
- tsconfig: extends the generated
$app/tsconfiginstead of.svelte-kit/tsconfig.json. - svelte-config: moves supported settings from
svelte.config.*intovite.config.*. - environment: replaces the legacy environment modules with
$app/env. - paths: migrates the removed
$app/pathsAPIs and path types. - external-redirects: opts external redirects into the new behavior.
- shallow-routing: replaces
pushStateandreplaceStatewithgotocalls. - params: gathers route parameter matchers into
src/params.ts(or.js). - app-state: moves
$app/storesusage to$app/state.
Other flags in the docs include --files to limit the run to a glob, --no-install to skip installing dependencies afterward, and --no-git-check to skip the dirty-tree prompt.
Step 3: Switch $lib to #lib by hand
None of the nine tasks covers the alias, and the release candidate post says import paths have to be updated manually. The migration guide shows the setup: add an imports field to package.json, then replace $lib with #lib across your code.
{
"imports": {
"#lib": "./src/lib/index.js",
"#lib/*": "./src/lib/*"
}
}
The catch is file extensions. Subpath imports don't guess, so $lib/utils becomes #lib/utils.ts, and a folder import becomes #lib/foo/index.ts. A project-wide find and replace gets you most of the way; your type checker will flag the imports that still lack an extension.
Step 4: Move environment variables to src/env.ts
The $env/* modules still work but are deprecated, and the environment variables docs say they will be removed in SvelteKit 4. The new system has you declare every variable in src/env.ts with defineEnvVars, now imported from @sveltejs/kit/env. Each entry can set:
public: safe to send to the browser (variables are private by default);static: inlined at build time, so the value is frozen to whatever existed during the build;schema: a validator function or a Standard Schema library such as Zod or Valibot;description: a note that shows up in your editor.
You then import private values from $app/env/private and public ones from $app/env/public. The docs say the app fails to start or build when validation fails, so a missing or invalid variable is caught when the app starts or builds, not later in production.
Step 5: Review your error handling
This is the step most likely to change behavior without breaking the build. According to the migration guide:
handleErrornow receives every error, including the ones you throw on purpose witherror().error()takes a string message as its second argument; any extra properties move to a third argument.handleValidationErroris removed. Validation errors reachhandleErrorwithkind: 'validation'.App.Erroralways includes astatus, andhandleErrorcan return astatusto set the HTTP code.- Rendering errors now go through
handleErrorand+error.svelteboundaries too, not only load errors. - Enhanced form actions respond with the status code you passed to
fail().
If your handleError logs to an error tracker, expect more events, since intentional 404s now pass through it. Filter on status if you don't want them.
Step 6: Fix navigation, cookies and adapters
The migration guide flags a set of smaller breaking changes worth a search through your code:
goto()now rejects when a URL doesn't resolve to a route in your app. Usewindow.location.hreffor outside links.invalidateAllis deprecated in favor ofrefreshAll(), which keepspage.state.redirect()to another site needs{ external: true }(the external-redirects migration task is meant to add this).- Cookies without an explicit
pathnow default to'/', so they apply site-wide. csrf.checkOriginis gone; CSRF protection is always on, withcsrf.trustedOriginsas the allowlist.json()andtext()are deprecated; useResponse.json()andnew Response(text).
Adapters changed too. The guide says adapter-node drops the ORIGIN environment variable in favor of paths.origin, adapter-vercel no longer supports the edge runtime, adapter-cloudflare needs wrangler ^4.67.0 and imports Cloudflare APIs from cloudflare:workers, and adapter-netlify needs Netlify CLI 17.31.0 or newer.
Step 7: Work through the to-do list and test
Finish with the list the migration tool generated, then run your type checker, your tests and a production build. Click through forms, redirects and any page that sets cookies, since those are the behavior changes a compiler won't catch. For more on frameworks and tools, see our software coverage and everything tagged SvelteKit.
Troubleshooting
- Imports fail to resolve after the switch. Check for a missing
.tsor.jsextension on#libimports, and confirm theimportsfield is in the rootpackage.json. - A redirect to another site throws. Add
{ external: true }, or a list of allowed origins, to theredirect()call. - A cross-origin form post is rejected. The guide says a missing
Content-Typeheader is now treated as CSRF; add the header or list the origin incsrf.trustedOrigins. - A 204 response lost its body. That's intended: empty 2xx responses now return no content, per the HTTP spec.
- Your service worker stopped finding
$service-worker. Importversionfrom$app/env, file lists from$app/manifestand paths from$app/paths.
Bottom line
We think that for most SvelteKit 2 apps, upgrading to SvelteKit 3 is a careful review rather than a rewrite: bump your toolchain, run sv migrate, do the #lib swap by hand, and spend real time on error handling, which is where behavior quietly changes. If you depend on remote functions, keep treating them as experimental. A Hacker News thread on the release had passed 170 comments as of October 3, 2026, with some commenters saying they already run remote functions in production; that's their call, not the Svelte team's guarantee. Watch the Svelte blog for the remote functions milestone next.
FAQ
Is SvelteKit 3 stable?
Yes. Version 3.0.0 was published as a stable release on October 1, 2026, after a release candidate announced on August 13. Remote functions inside it are still experimental.
Does sv migrate change $lib to #lib?
No. The documented sveltekit-3 tasks don't include it, and the Svelte team says import paths must be updated by hand. You add an imports field to package.json and replace the imports, with file extensions.
What Node version does SvelteKit 3 need?
The migration guide lists Node 22.17 as the minimum, alongside TypeScript 6 and Vite 8.0.12.
Do I have to move my environment variables right away?
Not immediately. The $env/* modules are deprecated in SvelteKit 3, not removed, and the docs say they will be removed in SvelteKit 4. Moving to src/env.ts now gets you validation and type safety.