· 8 min read

SvelteKit 3's Release Candidate Deletes the $lib Alias. The Migration Costs You Every Import in the Project.

SvelteKit 3 is in release candidate, and the change that will take the most of your afternoon is the smallest one in the announcement. The $lib alias is gone. Shared code in src/lib is now reached through Node subpath imports as #lib, and unlike the alias it replaces, the new form will not accept an ambiguous path. #lib/foo has to become #lib/foo.ts or #lib/foo/index.ts.

That is every shared import in your project. The migration tool handles most of it. What it cannot handle is the fact that SvelteKit 3 also requires Svelte 5 and Vite 8, which makes this three upgrades wearing one trench coat.

What actually changed

The alias swap is the headline, and the reasoning behind it is good. Subpath imports are a Node feature that Vite and TypeScript both support natively, which means the Svelte team gets to delete the code that used to keep those two tools agreeing about what $lib meant. Fewer moving parts in the framework is worth something, even when it costs you a find and replace.

Configuration moves too. svelte.config.js is gone and its contents live in vite.config.ts now. The team's argument is that the Vite plugin benefits from having the config immediately rather than resolving it asynchronously, which cannot begin until the whole Vite config resolves, which breaks when a tool like Vitest runs from a directory that is not the project root. Also, as they put it, why have config in two places.

A few other things worth knowing:

TypeScript setup got simpler. Instead of extending ./.svelte-kit/tsconfig.json you extend $app/tsconfig, which is generated into node_modules/$app. It carries more recommended compiler options than the old one, so you can probably delete most of your own compilerOptions.

Service workers stopped being a special case. The $service-worker module is gone and you import from $app/env, $app/paths, and a new $app/manifest like everywhere else in the app.

Error handling got materially better, and this is the change I would actually want. SvelteKit 2 supported Svelte 4, which had no error boundaries, so +error.svelte only rendered for errors during load, not during render. SvelteKit 3 requires Svelte 5, so that restriction is gone. Every error now routes through handleError, including ones you raised deliberately with error(...), which used to be skipped on the assumption you had already handled them. Stack traces get sourcemaps applied.

Shallow routing moved from pushState and replaceState to goto with shallow: true. Shallow navigations now fire beforeNavigate, and you can keep page state across a reload with persistState: true.

The migration command, and what it leaves you

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

This rewrites what it can and generates a TODO list for the rest. SvelteKit also prints diagnostic warnings and errors when you run code that has not been updated, which is the part that makes this survivable. You are not hunting silently broken imports.

For a new app:

npx sv@next create my-new-app

Remote functions are the reason to wait

The feature everyone is excited about is remote functions, which along with async Svelte is the team's answer to client and server communication. Their own words are that remote functions make everything else look clunky, including load and actions.

They are still behind an experimental flag.

That single fact decides the upgrade question for me. The marquee feature is not stable, the error handling improvements are nice but not urgent, and the cost is a three-way version bump across SvelteKit, Svelte, and Vite, plus rewriting every shared import in the project. You would be paying the full migration price now to get the boring half of the release.

What I'd actually do

If you have a SvelteKit app in production and it is working, wait for stable. The release candidate exists so that people who have time to test it find the problems, and the team says stable follows with no further breaking changes if all goes well. Let the people with time to spare find the sharp edges. Then upgrade once, into a version where remote functions might actually be usable.

If you are starting something new this week, start on SvelteKit 3. npx sv@next create gives you the new structure from the beginning and you never pay the $lib migration at all. The RC label is worth less than not doing a migration later.

If you want to help, or you have a small app where a broken afternoon costs nothing, run the migration and report what breaks. That is genuinely useful and it is how the stable release gets good. Just be honest with yourself about which category your app is in before you start.

One practical note for either path: Vite 8 means Rolldown, which is the thing most likely to surprise you. Faster builds are the pitch, but a build tool swap underneath a framework upgrade is where weird plugin incompatibilities live. If your build has unusual plugins, test that part first, before you spend time on imports.

Where this argument is weak

I am telling you to wait, and the counter-argument is decent. Migrations get harder the longer you defer them, and a codebase that skips SvelteKit 3 entirely will face a worse jump later. If your app is small, the $lib rewrite is a genuinely mechanical change that the tool mostly does, and being on the current major means you stop accumulating deferred work.

I am also assuming remote functions are what you want from this release. If error boundaries during render have been a real problem for you, that alone might justify the upgrade today, and my "boring half" framing undersells it.

What I am confident about: do not start this migration on a Friday, and do not start it in the same week you are shipping something else. A three-way version bump with a Rolldown swap underneath it deserves its own quiet week.

Author

Sources

Stay in the Loop

Get new posts delivered to your inbox. No spam, unsubscribe anytime.

Newsletter coming soon. Set PUBLIC_CONVERTKIT_FORM_ID in .env to activate.

Related Posts