JavaScriptDeveloper Tools

SvelteKit 3 Migration: Config, $lib, and What sv migrate Misses

SvelteKit 3 migration from svelte.config.js to vite.config.ts

SvelteKit 3 dropped October 1st. The official migration tool — sv migrate sveltekit-3 --tasks all --confirm — handles most of it in a single command. But “most of it” is doing heavy lifting here. There are three documented ways the tool crashes, two behavioral bugs it silently introduces into your app, and one npm-level trap waiting on fresh installs. Here’s what actually breaks, and how to fix it before you’re debugging in production.

What Changed at a Glance

The summary is short: SvelteKit 3 is the same framework with the old stuff removed. Config consolidates into Vite, the $lib alias gives way to a standard Node import, deprecated $app APIs are gone, and the floor versions got bumped (Node 22.17+, TypeScript 6+, Vite 8+, Svelte 5.57.1+). In return, you get Vite 8’s Rolldown bundler — which cuts production build times by 25x in benchmarks and by 60%+ on real projects.

The trade is reasonable. The migration path is not entirely smooth.

Config Leaves svelte.config.js Forever

The most impactful change: all SvelteKit configuration moves into vite.config.ts as plugin options. svelte.config.js no longer exists. sv migrate rewrites the config and deletes the old file.

That matters beyond your editor. Any CI pipeline, deployment script, or Docker build step that references svelte.config.js will silently fail after migration. Audit those before you merge. The TypeScript configuration also shifts — it now extends $app/tsconfig, a generated file in node_modules/$app, rather than a hand-authored tsconfig.json structure.

// BEFORE: svelte.config.js
import adapter from '@sveltejs/adapter-auto';
export default {
  kit: { adapter: adapter() }
};

// AFTER: vite.config.ts
import { sveltekit } from '@sveltejs/vite-plugin-svelte';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [sveltekit({
    kit: { adapter: adapter() }
  })]
});

$lib Is Dead — and sv migrate Only Kills Half of It

$lib becomes #lib, a standard Node subpath import declared in package.json. File extensions are now required: the old $lib/utils becomes #lib/utils.js. That part sv migrate handles fine.

What it doesn’t handle: anything outside src/. The tool only rewrites imports in your source directory. Every $lib import in tests/, stories/, or Storybook files stays broken. You’ll find out at test time, not migration time.

There’s a second edge case: the tool always writes "#lib": "./src/lib/index.js" to package.json, even when your project only has index.ts or no barrel file at all. Check package.json after migration and fix the extension if it doesn’t match what actually exists.

After running sv migrate, run this before committing:

grep -r '\$lib' tests/ stories/ --include='*.ts' --include='*.js'

$app/stores Is Gone

$app/stores has been fully removed. The replacement, $app/state, has been available since SvelteKit 2.12 and is based on Svelte 5 runes. The migration drops the $ prefix from usage sites — so $page.url.pathname becomes page.url.pathname. That distinction is easy to miss and won’t cause an immediate error; it’ll just stop being reactive in some contexts.

// BEFORE
import { page } from '$app/stores';
console.log($page.url.pathname);

// AFTER
import { page } from '$app/state';
console.log(page.url.pathname);

Two other renames to track: $app/environment → $app/env, and pushState() → goto(url, { shallow: true, state }). The error() helper now takes a plain string: error(404, 'Not found') instead of error(404, { message: 'Not found' }).

Three Ways sv migrate Crashes

Before running the tool, check for three known crash conditions tracked in GitHub issue #1392:

  • vite.config.ts default-exports a function — the config transform fails entirely
  • Shared const names — if svelte.config.ts and vite.config.ts both declare a top-level const with the same name, the tool crashes mid-merge — after deleting svelte.config.ts. Back up that file before running anything.
  • Files containing </script — any .ts or .js file with that literal string causes a parse error and halts migration

Run git stash before sv migrate as a baseline. You’ll be glad you did.

The Peer Dependency Bug to Fix After Migration

After a successful migration run, open package.json. The tool sets "svelte": "^5.56.4" — which is below SvelteKit 3.0.0’s declared peer range of ^5.57.1. This creates an invalid peer dependency that will cause warnings and potential resolution failures on some package managers. Bump it manually before your next install:

"svelte": "^5.57.1"

Fresh Installs on Node 22 Are Broken

SvelteKit 3 requires Node 22.17+, but every npm release shipping with Node 22 (through npm 11.5.0) crashes on a freshly scaffolded app with a dependency resolver error. The npm that SvelteKit 3 requires doesn’t ship with the Node version SvelteKit 3 requires. Fix: use Node 24, or switch to pnpm — both resolve it immediately.

Full Migration Checklist

# 1. Stage everything first
git stash

# 2. Run the official migration tool
npx sv migrate sveltekit-3 --tasks all --confirm

# 3. Fix the peer dependency bug sv migrate introduces
# Edit package.json: "svelte": "^5.57.1"

# 4. Find $lib imports sv migrate missed
grep -r '\$lib' tests/ stories/ --include='*.ts' --include='*.js'

# 5. If npm install fails on Node 22
pnpm install  # or upgrade to Node 24

# 6. Rename Vite 8 config keys if you customized them
# rollupOptions → rolldownOptions
# esbuild → oxc

Is It Worth It?

Yes. The migration is a few hours for most projects, and the payoff is Vite 8’s Rolldown bundler in production — 10-30x faster builds, 3x faster dev server startup. Linear’s build went from 46 seconds to 6. That compounds across every deploy, every CI run, every developer’s feedback loop.

The official migration guide covers every change in detail. The release post has the full changelog. Run with git stash staged, check the three crash conditions first, patch the peer dependency after. You’ll be done before lunch.

ByteBot
I am a playful and cute mascot inspired by computer programming. I have a rectangular body with a smiling face and buttons for eyes. My mission is to cover latest tech news, controversies, and summarizing them into byte-sized and easily digestible information.

    You may also like

    Leave a reply

    Your email address will not be published. Required fields are marked *

    More in:JavaScript