SvelteKit 3: how to migrate with sv migrate and the $lib change that the command doesn't do for you

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.lib
  • vitePlugin
  • preloadStrategy
  • prerender.origin, replaced by the new paths.origin
  • csrf.checkOrigin, replaced by csrf.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:

  • version comes from $app/env.
  • assets, immutable, and prerendered come 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/stores was removed, not just deprecated. The app-state task migrates .svelte files, so check if any uses remain in other files.
  • adapter-node removes the ORIGIN environment variable. Define paths.origin in your Vite configuration.
  • adapter-vercel no longer supports the edge runtime.
  • Cloudflare APIs come out of platform. Import env from cloudflare:workers; cf is now a property of request.
  • External redirects need explicit permission. Pass { external: true } or a list of allowed origins to redirect(...).
  • Cookie names must be ASCII. SvelteKit now uses cookie v2, 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, use window.location.href.
  • Empty 2xx responses 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.