SvelteKit 3: How to Migrate with sv migrate and the $lib to #lib Change That the Command Won’t Do for You
SvelteKit 3.0 was released on October 1, 2026 and almost the entire migration is automated with a command, npx sv migrate sveltekit-3. What that command doesn’t do is change $lib to #lib: that step is manual. In this guide you’ll see what changes, what the migration tool resolves, and what you need to do yourself.
The Svelte team presents it as the same framework with more polish and better type safety. For the everyday API it’s true, but the project structure changes: the configuration moves to a different file, the most commonly used import alias gets replaced, environment variable modules become obsolete, and service workers are configured differently.
What is SvelteKit and How Does It Differ from Svelte?
SvelteKit is the official application framework for Svelte: it handles routing, data loading, server rendering, and deployment via adapters. It is to Svelte what Next.js is to React. Svelte compiles the components and SvelteKit turns them into a deployable application, whether with server rendering, prerendered as a static site, or as a single-page app. It’s licensed under MIT and developed at github.com/sveltejs/kit.
What Changes in SvelteKit 3?
The official announcement highlights five changes, and four of them affect files you already have in your project.
Configuration Moves from svelte.config.js to vite.config
svelte.config.js is no longer supported. Configuration is now passed directly to the sveltekit plugin in Vite, and the options that previously lived in config.kit.* become top-level plugin options:
import adapter from '@sveltejs/adapter-auto';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
sveltekit({
adapter: adapter()
})
]
});
Options that aren’t SvelteKit-related are forwarded to vite-plugin-svelte. With the move, several options disappear:
files.libvitePluginpreloadStrategyprerender.origin, replaced by the newpaths.origincsrf.checkOrigin, replaced bycsrf.trustedOrigins
$lib Becomes #lib
SvelteKit no longer generates the $lib alias. Instead, you declare #lib yourself using Node.js subpath imports, which Vite and TypeScript resolve natively. This is the change that sv migrate doesn’t do for you; the steps are further down.
Environment Variables Move to $app/env
The $env/... modules become obsolete in favor of $app/env/private and $app/env/public. $app/environment is renamed to $app/env, and you can now import it inside a service worker as well.
Service Workers Need Less Boilerplate Code
The $service-worker module was removed and its exports are distributed among standard modules:
versioncomes from$app/env.assets,immutable, andprerenderedcome from the new$app/manifest.- Route utilities come from
$app/paths.
A new module, $app/service-worker, gives you a typed service worker context. Also, SvelteKit now registers the service worker as an ES module rather than a classic script.
The fifth highlighted change is error handling. In SvelteKit 3, handleError receives all errors, including expected ones you create with error(...), and rendering errors are sent to the nearest +error.svelte.
What Do You Need Before Migrating to SvelteKit 3?
According to the official migration guide, SvelteKit 3 requires these minimum versions:
| Dependency | Minimum |
|---|---|
| Node | 22.17 |
| TypeScript | 6 |
| Svelte | 5.57.1 |
| Vite | 8.0.12 |
@sveltejs/vite-plugin-svelte |
7 |
The Svelte team recommends updating first to the latest 2.x version, because that version shows deprecation warnings aimed exactly at the code you’ll need to change. Commit your project before you start: if the working tree has uncommitted changes, sv migrate will warn you.
How to Migrate to SvelteKit 3 with sv migrate?
The release announcement suggests a command that does everything at once:
npx sv migrate sveltekit-3 --tasks all --confirm
The CLI documentation recommends a more cautious approach: run one task at a time and commit after each one, so each diff is easy to review. To see available tasks without running anything, use:
npx sv migrate sveltekit-3 --tasks
The sveltekit-3 migration consists of nine tasks. The first two are prerequisites and always run:
| Task | What It Does |
|---|---|
package-json |
Updates package versions for SvelteKit 3 |
tsconfig |
Makes your tsconfig.json extend $app/tsconfig instead of .svelte-kit/tsconfig.json |
svelte-config |
Moves supported options from svelte.config.* to vite.config.* |
environment |
Replaces old environment modules with $app/env and creates src/env.js or src/env.ts declarations when needed |
paths |
Migrates obsolete $app/paths APIs (base, assets, and resolveRoute are replaced by asset() and resolve()) |
external-redirects |
Adapts external redirects to the new behavior |
shallow-routing |
Replaces pushState and replaceState with goto(..., { shallow: true }) |
params |
Consolidates param matchers into a single src/params.js or src/params.ts |
app-state |
Migrates $app/stores to $app/state |
To run a single task:
npx sv migrate sveltekit-3 --tasks svelte-config
The documentation warns that the project doesn’t necessarily have to work if you only ran some tasks: most applications need all the ones that correspond to them. The tool only rewrites patterns it can identify with certainty. Where it can’t, it leaves a @migration-task comment, so search for that exact marker in the project before considering the migration complete.
How to fix $lib after migrating to SvelteKit 3?
$lib doesn’t appear in the task list. When sv migrate finishes, every import with $lib in your code still points to an alias that SvelteKit 3 no longer generates. To fix it, you need three steps.
1. Declare #lib in package.json:
{
"imports": {
"#lib": "./src/lib/index.js",
"#lib/*": "./src/lib/*"
}
}
2. Replace $lib with #lib throughout your code.
3. Add the file extension to those imports. Subpath imports require it, so the change isn’t a simple find-and-replace of the prefix:
// before
import { foo } from '$lib/foo';
// after
import { foo } from '#lib/foo.js';
The files.lib option also disappeared: if you had customized your lib directory, point the imports entries to that directory.
What else can break in production?
These changes from the migration guide modify runtime behavior, and some produce no build error:
$app/storeswas removed, not just deprecated. Theapp-statetask migrates.sveltefiles, so check if any uses remain in other files.adapter-noderemoves theORIGINenvironment variable. Definepaths.originin your Vite configuration.adapter-vercelno longer supports theedgeruntime.- Cloudflare APIs come out of
platform. Importenvfromcloudflare:workers;cfis now a property ofrequest. - External redirects need explicit permission. Pass
{ external: true }or a list of allowed origins toredirect(...). - Cookie names must be ASCII. SvelteKit now uses
cookiev2, which rejects non-ASCII characters likeáin cookie names. It’s worth checking in applications in other languages. - Cookies use
path: '/'by default, instead of the current request path. error(...)receives a string as the second argument. Additional properties now go in a third argument.goto()rejects URLs that don’t correspond to a route in your application. To navigate to an external site, usewindow.location.href.- Empty
2xxresponses are returned without a body, as required by the HTTP specification.
Are remote functions ready in SvelteKit 3?
Not yet. At the time of publishing this note, remote functions were still experimental: they require the experimental.remoteFunctions flag along with Svelte’s async compiler option. The Svelte team marks them as their highest priority. If you were waiting for them, it’s still worth updating now, because the migration doesn’t depend on them.
If you’re following other major updates this season, we already covered what changes in htmx 4.0 if you’re coming from htmx 2.