Skip to content
Nilay Kabariya

Entry No.044·Tested··4 min read

SvelteKit 3 migration, tested: what sv migrate misses

I upgraded a working SvelteKit 2 app to 3.0.1 with the official sv migrate. It reported success, then the build failed three times and one link quietly broke.

by Nilay#sveltekit#svelte#vite#typescriptTESTED

Verdict

sv migrate sveltekit-3 upgraded every package and moved the config correctly, but left three removed options in place, so the first build failed three times in a row. Worse, it rewrote a working {base}/about link into one that 404s, and that passed both the build and svelte-check.

Tested on

SvelteKit
2.40.0 → 3.0.1
Svelte CLI
sv 1.1.1
Vite
7.3.7 → 8.3.3
Node
24.14.0
OS
Windows 11 Pro
Contents
  1. What changed in the packages01
  2. Way 1: just bump the package02
  3. Way 2: the official migration03
  4. The build failed three times04
  5. The bug the build didn’t catch: a link that 404s05
  6. A behaviour change: fail() now returns its status code06
  7. TypeScript 7 with SvelteKit 307
  8. Checklist after sv migrate sveltekit-308
  9. Limits of this test09
  10. How this was tested10

SvelteKit 3.0 shipped on October 1, 2026, with about thirty breaking changes and an official migration command. I took a SvelteKit 2.40 app of the kind most people still run (Vite 7, TypeScript 5.9, a svelte.config.js, and a few of the APIs that changed), confirmed it built and type-checked with 0 errors, then upgraded it the two ways people will try.

What changed in the packages

SvelteKit 3.0.1 refuses to install next to older tooling. Its peer requirements:

Package Needs
vite ^8.0.12
svelte ^5.57.1
typescript ^6.0.0
@sveltejs/vite-plugin-svelte ^7.0.0
@sveltejs/adapter-auto 8.0.0 (version 6 and 7 only accept Kit 2)

It also needs Node 22 or newer.

Way 1: just bump the package

npm install -D @sveltejs/kit@latest
npm error code ERESOLVE
npm error Found: @sveltejs/vite-plugin-svelte@6.2.4
npm error Could not resolve dependency:
npm error peer @sveltejs/vite-plugin-svelte@"^7.0.0" from @sveltejs/kit@3.0.1

Nothing is installed. You’d have to bump Vite, Svelte, TypeScript, the Vite plugin and the adapter together, which is what the migration tool does for you.

Way 2: the official migration

npx sv migrate sveltekit-3

The new Svelte CLI (sv 1.0, released the same day as Kit 3) offers ten tasks. Its own advice is that “most projects need all applicable tasks”, so I ran them all (--tasks all). It finished with All tasks applied successfully! and:

  • upgraded all seven packages to the versions in the table above,
  • moved svelte.config.js into vite.config.ts, inside the sveltekit({ … }) plugin call,
  • rewrote $app/paths imports (base, resolveRoute → resolve),
  • moved the Handle type import to @sveltejs/kit/hooks,
  • replaced tsconfig.json with one that extends $app/tsconfig,
  • added #lib subpath imports to package.json,
  • wrote a MIGRATION_TASKS.md listing work it didn’t automate.

Then I ran the build.

The build failed three times

Each fix revealed the next error.

1. The CSRF option. The tool moved it into vite.config.ts unchanged:

config_option_removed_check_origin
`config.csrf.checkOrigin` has been removed in favour of `csrf.trustedOrigins`
error during build:
[Error: Failed to load SvelteKit options from Vite config]

2. The preload option. Same story, and this one isn’t mentioned in MIGRATION_TASKS.md at all:

config_option_removed_preload_strategy
`config.output.preloadStrategy` has been removed. `modulepreload` will always be used
error during build:
[Error: Failed to load SvelteKit options from Vite config]

3. The Node polyfills. Left in hooks.server.ts:

"./node/polyfills" is not exported under the conditions ["module", "node", "production", "svelte", "import"]
from package …\node_modules\@sveltejs\kit

Delete all three. None has a replacement you need for a normal app:

  • csrf: { checkOrigin: true }: remove it. Checking the origin is the default. Only list csrf.trustedOrigins if other sites must be allowed to post your forms.
  • output: { preloadStrategy: … }: remove it. SvelteKit 3 always uses modulepreload.
  • import { installPolyfills } from '@sveltejs/kit/node/polyfills' and the installPolyfills() call: remove both. Node 22+ already has what it provided.

My Kit 2 page had a normal base-path link:

<a href="{base}/about">About</a>

On Kit 2.40 it rendered href="/about". The migration rewrote it to:

<a href="{resolve('')}/about">About</a>

On Kit 3.0.1 that rendered as href=".//about", which a browser resolves to http://localhost/ + //about, a 404. From a nested page like /blog/post it becomes /blog//about. It passed vite build and svelte-check with no warning. The tool did list +page.svelte under “Files to review” in MIGRATION_TASKS.md, but only as a generic $app/paths check, after already writing the broken version.

Search your project for resolve('') after migrating. Put the whole path inside resolve:

<a href={resolve('/about')}>About</a>

A behaviour change: fail() now returns its status code

My form action returns fail(400, { missing: true }) when a field is empty. Same action, same empty field:

Kit 2.40 Kit 3.0.1
Plain form POST (no JS) HTTP 200 HTTP 400
use:enhance (fetch) HTTP 200, "status":400 inside the JSON HTTP 400

The page and the returned form data look the same, so users won’t notice. Things that read the HTTP status will: end-to-end tests asserting 200, uptime or error-rate monitoring, and logs that treat 4xx as errors.

TypeScript 7 with SvelteKit 3

Kit 3’s typescript: ^6.0.0 excludes TypeScript 7. The same side-by-side setup from my TypeScript 7 frameworks test works here too:

npm install -D "@typescript/native@npm:typescript@7.0.2" "typescript@npm:@typescript/typescript6@^6.0.2"

Checklist after sv migrate sveltekit-3

  1. Run vite build before anything else, and fix config errors one by one (checkOrigin, preloadStrategy and any other removed option).
  2. Delete @sveltejs/kit/node/polyfills imports.
  3. Search for resolve('') and move the path inside resolve(…).
  4. Work through MIGRATION_TASKS.md and any @migration-task comments.
  5. Check tests and monitoring that expect 200 from failed form actions.
  6. Run the app and click the links. A green build proved nothing about item 3.

Limits of this test

One small app, built to touch the changes most likely to bite: config options, $app/paths, hooks and form actions. It didn’t cover remote functions, shallow routing, parameter matchers, custom adapters or $app/stores, which the migration also rewrites. The release notes list about thirty breaking changes. Read the official migration guide for the rest.

How this was tested

A SvelteKit 2.40.0 app (adapter-auto 6.1.1, Vite 7.3.7, vite-plugin-svelte 6.2.4, TypeScript 5.9.3) that built and passed svelte-check with 0 errors, upgraded with npm install and then with npx sv@1.1.1 migrate sveltekit-3 --tasks all. After each change I ran vite build and svelte-check, then served both versions with vite preview and checked the rendered links and the form action’s HTTP status with curl. Every message above is copied from those runs.

— N.K., end of entry No.044

Useful? Pass it on:Post on XFollow @EmotionalMatter

Related entries

  1. No.030

    Does Next.js, Angular, Vue or NestJS work with TypeScript 7?

    Next.js 16.3.6 built and type-checked on plain TypeScript 7.0.2. Angular 22.2, Vue with vue-tsc 3.3 and NestJS 12 all failed, and all three built again on Microsoft's side-by-side setup, with tsc still on 7.0.2. Nuxt, Svelte and Astro still built on 7, but their type-check commands refused it until TypeScript 6 was installed alongside.

    TESTED5 min
  2. No.010

    Fix: Vite “Failed to resolve import” for @/ path aliases

    Vite can't resolve your @/ imports even though tsconfig looks right. The config that fixes it and the settings that don't. Reproduced on Vite 8.

    > [vite] Internal server error: Failed to resolve import "@/utils/hello"

    FIXED1 min
  3. No.031

    TypeScript 7 migration guide: breaking changes, tested

    Every option TypeScript 6.0.3 marked as deprecated was a hard error on 7.0.2, and ignoreDeprecations no longer silences it. Two defaults that arrived in 6.0 (strict on, @types not loaded automatically) also break projects coming straight from 5.9.

    TESTED6 min

Post card · Newsletter

Get the next fix in your inbox.

One short email when a new entry is published. No spam, never shared, and you can leave any time.

— Nilay

or follow by RSSor on X

By subscribing you agree to the privacy note. One click to leave.

tip: paste the exact error text