Skip to content
chornous.dev

Type two or more letters. Esc closes.

Index of sheets
Theme

Sheet 03 · Writing

All notes

SvelteKit 3: config moves into vite.config, $lib becomes #lib, and Svelte 4 support ends

The Svelte team shipped SvelteKit 3.0 on October 1, 2026. It drops svelte.config.js, replaces $lib with a Node subpath import, requires Node 22.17, TypeScript 6, Svelte 5.57.1 and Vite 8, and removes $app/stores and $service-worker.

Pl. 19 · pipeline drawing generated from the slug “sveltekit-3-vite-config-lib-alias”

The Svelte team released SvelteKit 3.0 on October 1, 2026, seven weeks after the release candidate went out on August 13. The sv CLI reached 1.0 the same day. The team describes the new major as the same framework with "a little more polish, a little more type safety, and a little less junk."

The junk it removed sits in your codebase, though. A typical SvelteKit 2 app has a svelte.config.js, $lib imports and a $app/stores import or two, and SvelteKit 3 changes all of them.

Minimum versions

SvelteKit 3 requirements, from the migration guide
DependencyMinimum
Node22.17
TypeScript6
Svelte5.57.1
Vite8.0.12, the first Vite 8 release with stable Rolldown 1
@sveltejs/vite-plugin-svelte7

The Svelte 5 requirement pays for the error-handling change. SvelteKit 2 still supported Svelte 4, which had no error boundaries, so +error.svelte caught errors thrown during load and missed render errors. SvelteKit 3 catches rendering errors too, sends errors you raise with error(...) through handleError, and applies sourcemaps to stack traces.

Run the migration

The team recommends upgrading to the latest 2.x release first, so you see the deprecation warnings, then running the codemod:

npx sv migrate sveltekit-3 --tasks all --confirm

It rewrites what it can and leaves a TODO list for the rest. You will want to review three changes by hand.

Config lives in the Vite plugin

svelte.config.js no longer works. Options that sat under kit become top-level options of the sveltekit() plugin:

// vite.config.js
import { defineConfig } from 'vite';
import { sveltekit } from '@sveltejs/kit/vite';
import adapter from '@sveltejs/adapter-auto';

export default defineConfig({
  plugins: [
    sveltekit({
      compilerOptions: { experimental: { async: true } },
      adapter: adapter()
    })
  ]
});

Several options went away in the move. preloadStrategy is gone because SvelteKit uses modulepreload in all cases, prerender.origin became paths.origin, and csrf.checkOrigin became csrf.trustedOrigins. If you run adapter-node behind a proxy, paths.origin replaces the ORIGIN environment variable.

$lib is a subpath import

SvelteKit stopped generating the $lib alias. You declare #lib in package.json, and Node, Vite and TypeScript resolve it without framework glue:

{
  "imports": {
    "#lib": "./src/lib/index.js",
    "#lib/*": "./src/lib/*"
  }
}

Imports need file extensions now, so $lib/foo becomes #lib/foo.js. Your tsconfig.json also changes, from extending ./.svelte-kit/tsconfig.json to extending $app/tsconfig, with explicit include and exclude arrays.

Removed modules

Modules removed or renamed in SvelteKit 3
SvelteKit 2SvelteKit 3
$app/storesremoved, use $app/state
$app/environmentrenamed to $app/env
$service-workerremoved, use $app/env, $app/manifest and $app/paths
base, assets, resolveRoute in $app/pathsremoved, use asset() and resolve()
$env/* modulesdeprecated in favor of $app/env/private and $app/env/public

Behavior changes that won't show up as errors

A few changes compile fine and still alter what users see.

  • version.pollInterval defaults to one hour, so updated.current flips on its own after a deploy. If you show an update banner, expect it to appear more often.
  • Shallow routing moved to goto(url, { shallow: true }), and shallow navigations now fire beforeNavigate, onNavigate and afterNavigate. If your analytics run in those hooks, filter on the shallow property.
  • goto rejects URLs that don't match a route in your app.
  • preloadData returns { type: 'error' } for failed pages instead of a 200 loaded result.
  • Forms using use:enhance with an action on another page now navigate there.

Adapter users have their own list. The Cloudflare adapter removed the platform object in favor of an emulated cloudflare:workers module, and adapter-node serves static assets present at build time and nothing added later, with content-hash ETags.

Remote functions are still experimental

Remote functions, SvelteKit's type-safe client-server calls, stay behind an experimental flag and need Async Svelte, which has its own flag. The team calls them its top priority. Don't plan a rewrite of load functions and form actions around them yet.

This week

  1. Upgrade to the newest 2.x and clear the deprecation warnings.
  2. Check Node in CI and production. Anything below 22.17 blocks the upgrade.
  3. Run npx sv migrate sveltekit-3 --tasks all --confirm on a branch, then work through the TODO list.
  4. Search for $app/stores, pushState and invalidateAll, and test navigation-heavy pages by hand.

If you deploy to Cloudflare and read bindings from platform, budget extra time for that adapter change before you merge.

Volodymyr Chornous