> sveltekit 3 — config in vite.config.ts, #lib imports, and a tidy $app

// october 9, 2026

SvelteKit 3 — Config in vite.config.ts, #lib Imports, and a Tidy $app

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 #lib natively — no extra paths or alias config.
  • TypeScript does not resolve extensionless #lib/* imports the way it does for relative paths, so get used to writing .js (which maps to your .ts files).
  • .svelte and 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/environment is deprecated; import browser, dev, and building from $app/env instead.

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.json now extends the framework's own $app/tsconfig, keeping verbatimModuleSyntax, 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.