SvelteKit 3 — Config in vite.config.ts, #lib Imports, and a Tidy $app
Version 3.0 of SvelteKit landed at the start of October, and — true to the team's word — it's the same framework with a little more polish, a little more type safety, and a little less junk. I spent an afternoon migrating this site and walked away genuinely impressed with how smooth the process was.
The headline: svelte.config.js is gone, $lib is now #lib, and the $app/paths module finally dropped the manual base string-concatenation dance.
The Migration Path
For once, a major version bump doesn't mean rewriting your app. The team shipped a dedicated migration command:
npx sv migrate sveltekit-3 --tasks all --confirm
It rewrites what it can and prints a TODO list for everything that needs a human eye. To start fresh:
npx sv create my-new-app
Configuration Moves to vite.config.ts
The most visible change: svelte.config.js is no longer read. Everything the SvelteKit plugin needs — adapter, preprocessors, extensions, paths — moves into the sveltekit() plugin inside vite.config.ts:
// vite.config.ts
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
import adapter from "@sveltejs/adapter-static";
export default defineConfig({
plugins: [
sveltekit({
adapter: adapter(),
preprocess: [vitePreprocess(), mdsvex()],
extensions: [".svelte", ".svx"],
paths: { base: "/mborowski" },
}),
],
});
One fewer config file, and your Vite and SvelteKit configuration finally live in the same place.
$lib Becomes #lib
$lib is retired in favour of #lib, which leans on Node's standard subpath imports. Declare the mapping once in package.json:
{
"imports": {
"#lib": "./src/lib/index.js",
"#lib/*": "./src/lib/*"
}
}
Then the old alias just works with a #:
import { getPageData } from "$lib/content.server"; // before
import { getPageData } from "#lib/content.server.js"; // after
A few things worth knowing:
- Vite and TypeScript both resolve
#libnatively — no extrapathsoraliasconfig. - TypeScript does not resolve extensionless
#lib/*imports the way it does for relative paths, so get used to writing.js(which maps to your.tsfiles). .svelteand asset imports keep their real extensions:import Nav from "#lib/components/Nav.svelte".
A Tidy $app/paths
This is the change I appreciate most. For years the pattern for a site with a base path was manual string concatenation:
import { base } from "$app/paths";
const href = `${base}/about`;
SvelteKit 3 removes base and assets from the public API and replaces them with two purpose-built helpers:
import { resolve, asset } from "$app/paths";
resolve("/about"); // route ID -> /base/about
resolve("/blog/[slug]", // route + params -> /base/blog/hello-world
{ slug: "hello-world" });
asset("img/avatar.webp"); // static file -> /base/img/avatar.webp
resolve handles both pathnames and route IDs (with params), and asset targets files in your static directory. The real payoff is correctness during prerendering: resolve returns a relative path on the server, so your static site works no matter which subpath it's deployed under. No more hard-coded /mborowski/... links that break when the repo changes hands.
There are two traps to avoid:
- A leading
/means "route ID", not "pathname" —resolve("/about")is a route,resolve("about")is a path. $app/environmentis deprecated; importbrowser,dev, andbuildingfrom$app/envinstead.
The Smaller Wins
The release notes call out a few more quality-of-life improvements:
- Environment variables — public and private env vars are now declared explicitly (and typed), rather than inferred from a naming convention.
- Service workers — less boilerplate to register and type a worker.
- Error handling — improved messaging and a more consistent shape across server and client boundaries.
- TypeScript — your
tsconfig.jsonnow extends the framework's own$app/tsconfig, keepingverbatimModuleSyntax,isolatedModules, and friends in sync automatically.
Remote Functions Are Coming
The most exciting thing in the announcement is what isn't quite ready: remote functions. The idea is secure, efficient, type-safe client–server communication without hand-rolled endpoints. They depend on Async Svelte, which still sits behind an experimental flag — but the team calls it their top priority.
I'm holding off on adopting them in production until the flag disappears, but the direction is exactly what I want: less ceremony, more type safety.
Final Thoughts
Migrating this site to SvelteKit 3 took an afternoon, and the result is a smaller surface area: one fewer config file, a standard import alias, and a routing API that no longer asks me to hand-assemble base-path strings.
If you're on SvelteKit 2, run sv migrate and see how far it gets you — the TODO list it leaves behind is usually short, and the remaining work is the kind of thing an afternoon (or a friendly coding agent) can finish. Version 3 feels less like a rewrite and more like the framework finally tidying up after itself.