Skip to content
Published on

Reading the SvelteKit 3.0 Pre-Release — What Breaks, What Tightens, and What Is Still Experimental

Share
Authors

Introduction — No Announcement Post, It Just Showed Up on npm First

There is not yet a "Introducing SvelteKit 3" post on the svelte.dev blog. But the trail on the npm registry is already unmistakable — @sveltejs/kit's 3.0.0-next.0 went up on June 5, 2026, and as of the time of this writing (2026-07-17) the latest pre-release is 3.0.0-next.8 from July 14. Nine pre-releases in under six weeks, running on two tracks side by side with the stable 2.69.3 (July 13).

The repository tells the same story. sveltejs/kit has an open version-3 branch, and the 3.0 milestone shows 107 closed issues and 27 open issues as of today. No stable release date has been announced — so everything in this post should be read as "a snapshot as of next.8." It is a pre-release; things can still change from here.

But read the version-3 branch's changelog straight through from next.0 to next.8 and one thing becomes clear. This major has almost no new features. Instead it settles two years of deprecation notices, raises the toolchain floor, and tightens defaults. And the things the community expects to be the face of SvelteKit 3 — remote functions, await inside components — are still experimental even in the v3 pre-release. Understanding this framing is half the migration decision.

Timeline — Where 2.x Has Already Gotten To

To read v3, you first need the tail end of 2.x. All dates below are the publish timestamps from the npm registry.

2024-12-16  kit 2.12   $app/state added (rune-based — successor to $app/stores)
2025-07-14  svelte 5.36  component await (experimental.async flag)
2025-07-24  kit 2.26   resolve()/asset() added to $app/paths (replaces old base/assets)
2025-07-31  kit 2.27   remote functions (experimental flag)
2026-01-22  svelte 5.48  ← the release that later becomes kit v3's minimum Svelte version
2026-05-01  kit 2.59   query.live added (experimental)
2026-05-22  kit 2.61   query.live becomes an async iterable, query .run() removed (breaking)
2026-06-02  kit 2.62   config can be passed to the Vite plugin without svelte.config.js
2026-06-04  kit 2.63   explicit environment variables (experimental)
2026-06-05  kit 3.0.0-next.0  ← pre-release begins
2026-07-14  kit 3.0.0-next.8  ← current

Two things stand out. First, 2.62 and 2.63 are the "trailers" that shipped right before v3 — in the words of the July digest, moving config into vite.config and explicit environment variables both landed in 2.x first as a "preview of how this will work in Kit 3." Second, as 2.61 shows, features tagged experimental keep taking breaking changes even in minor releases. Removing .run(), changing the requested(...) signature — these shipped as semver minors. If you turned the flag on, you stepped outside the zone semver protects.

The Floor v3 Raises — Minimum Runtime and Toolchain

These are the minimum requirements pinned down by next.0's changelog.

  • Node 22 or later (#12548)
  • TypeScript 6 or later (#15930) — npm has stable 6.0.2/6.0.3, and above that TypeScript 7.0 has already gone GA (7.0.2, 2026-07-08). Note that svelte-check 4.7.0 also has experimental tsgo (native TS) support.
  • Vite 8 — this started as a Vite 8 requirement (#15371), then was tightened to vite@^8.0.12 in next.5. In the changelog's own words, this is because it's "the first Vite 8 release to bundle stable rolldown 1.0.0" (#16134). Cross-checking npm dates: rolldown 1.0.0 shipped 2026-05-07, vite 8.0.12 shipped 2026-05-11 — four days apart, locked together. In other words, SvelteKit 3 only runs on top of the Rust bundler.
  • @sveltejs/vite-plugin-svelte 7 (#15371)
  • Svelte 5.48.0 or later — this matters. Not Svelte 6. 5.48 is an ordinary minor from January 2026. v3 is not coupled to Svelte's generational shift.

The changelog cites "faster builds through Vite hook filters and stronger adapters that use the Vite environment API" as the rationale for the Vite 8 requirement — no concrete build-time figures have been published anywhere, so I'll only note here that this is the project's own framing.

APIs Being Removed — Settling Two Years of Deprecation Notices

These are the things removed or renamed in v3. In every case a successor API already shipped in 2.x first, so there is effectively no "sudden removal."

Going awaySuccessorWhen the successor landed
the entire $app/stores module (#15499)$app/statekit 2.12 (2024-12)
base/assets/resolveRoute from $app/paths (#15507)resolve()/asset()kit 2.26 (2025-07)
invalidateAll (deprecated) (#16289)refreshAll (also applies to the goto option)newly introduced in v3 next.8
$app/environmentrenamed to $app/env (alias kept, next.1)v3
the four $env/* modulesexplicit environment variables ($app/env/private/$app/env/public)kit 2.63 (experimental)
svelte.config.js requiredpass config to the Vite plugin (#16007)possible since kit 2.62
param files inside a foldera single params.js/ts file (#16189)v3
the preloadStrategy optionalways modulepreload + output.linkHeaderPreloadv3
CSRF checkOrigintrustedOriginsalready deprecated within kit 2.x
@sveltejs/kit/node/polyfills, createEntries for adaptersnone (removed)

A few points worth flagging.

Removing $app/stores is the final period on the rune transition. Svelte 5 runes pushed stores out long ago, but SvelteKit's page/navigating/updated stores stayed alive for backward compatibility. Its successor $app/state has now had a year and a half out in the world, so as retirements go, this one is on the gentle side.

invalidateAllrefreshAll is a vocabulary unification. Read PR #16289 and the intent is clear — in the world of remote functions, refreshing data is query.refresh(), and that vocabulary had drifted apart from "invalidation" in the world of load functions. refreshAll is the name that folds both into one word, and the option to always rerun load functions was cleaned up alongside it. One thing to note: refreshAll does not exist in 2.69.3. It's something you switch to when you move up to v3, not something you can do today.

Environment variables move from module magic to explicit declaration. The docs' preview language is unambiguous — explicit environment variables become the default in v3, and the $env/* modules and $app/environment are removed. The new approach is declared in src/env.ts.

// src/env.ts — you can try this early on kit 2.63+ via experimental.explicitEnvironmentVariables
import { defineEnvVars } from '@sveltejs/kit/hooks';

export const variables = defineEnvVars({
	API_KEY: {}, // private by default — importable only from $app/env/private
});

Where $env/static/private used to be the clever trick of "enforcing exposure by way of import location," the new approach pins down the entire list of environment variables that exist in the app, with types, in a single file. The problem of not being able to tell what variables exist just by reading the code goes away — in exchange, every new variable means one more line of declaration.

Defaults Changing Quietly — Mostly on the Security Side

These are quieter than removals, but they actually change behavior. This is usually where migration time gets eaten.

  • External redirects are banned by default (#16198). To redirect to an external URL (or a javascript: URL) you now have to explicitly pass the external option. If you have code that redirects post-login via a ?next= parameter — a classic open-redirect spot — leaving it as-is in v3 will break it. Breaking is the point.
  • The default cookie path becomes '/' (#15398). Until now it was based on the current path, which was the source of the classic "I set a cookie but it's not visible on another page" question. Adopting cookie v1 also restricts cookie names to ASCII (#13386).
  • Form action failures use the HTTP status code carried in fail() as the actual response code (#16200). This used to arrive riding on a success 200, so this changes — anything watching status codes, like a proxy or monitoring setup, is affected. 204 responses now genuinely go out with no body.
  • handleError can now affect the response status code (#16162).
  • goto rejects on a URL that can't be resolved to an app route (#16164) — matching behavior it already had for external URLs.
  • Calling invalidate during navigation no longer aborts the navigation (#16188).

For what it's worth, the fix that forces cache-control: private, no-store on remote function responses — so personalized query results don't stick around in a shared cache — already landed in 2.65.2, not v3. Tightening security defaults isn't a v3-only theme; it's been the consistent direction of the last several months.

What's Still Experimental — v3 Is Not a Feature Major

This is the most important section of this post.

Remote functions are still experimental even in v3. The official docs still say "currently experimental… subject to change without notice," and require two flags: kit.experimental.remoteFunctions and compilerOptions.experimental.async. What's more telling is the direction of the v3 pre-releases — next.7 actually banned putting *.remote.ts/js files in place without the flag (#16247, merged 2026-07-07). That is not a change you'd make if stabilization were imminent. Nearly a year after shipping as 2.27 in July 2025, and even past the v3 major, there is no API stability guarantee for remote functions.

Component await is the same story. It's been usable behind the experimental.async flag since Svelte 5.36 (2025-07-14), and the docs spell out exactly when the flag comes off — "The experimental flag will be removed in Svelte 6." And Svelte 6 does not exist on npm — no 6.x has ever shipped, stable or pre-release, and svelte's latest is 5.56.6 (2026-07-16). The fact that kit v3's minimum requirement is Svelte 5.48 sums up this framing: SvelteKit's major version and Svelte's major version are separate trains. The compiler side's experimental list also still holds the fork API (5.42) and async SSR (5.39).

There is one graduation running the other direction — OpenTelemetry tracing left the experimental namespace and became a stable API in next.7 (#16260).

To sum up, this is what v3 actually is: every feature that's generating buzz is shipping simultaneously to both 2.x and v3 on the experimental track, and the major version is spent swapping out the floor underneath — runtime, bundler, names, defaults. The expectation that "remote functions will stabilize once you move to v3" does not hold up against the current evidence.

A Contrast With the Neighborhood — Majors Do Different Jobs

It's an interesting contrast that the news that lit up the React ecosystem the same week was the Rust port of the React Compiler. React is building new compiler infrastructure from scratch to eliminate manual memoization — and that work is still at an experimental WIP stage — while Svelte has had a compiler as its foundation from day one, so it never had that stage to go through. Instead, Svelte's major is spent raising the bundler (rolldown) and runtime floor. Both frameworks share a common thread in 2026: spending major-version-level energy on foundation work rather than a "big feature dump," and neither one is about to substantially change an app developer's code anytime soon.

So What Should You Do Right Now

What you can do today, on 2.x, ahead of time — all of it is prepayment toward the v3 migration.

  • Move $app/stores imports to $app/state. It's a year-and-a-half-old API by now, so there's plenty of reference material.
  • Replace string concatenation of base/assets from $app/paths with resolve()/asset().
  • Try moving config into vite.config's plugin options (2.62+). If you're starting a new project, sv CLI 0.16 already scaffolds it that way.
  • Try moving to Node 22, Vite 8, and vite-plugin-svelte 7 first — this is a validation you can do right now, without v3.
  • Map out where you use invalidateAll. Actually switching happens at v3, but knowing where it's used is something you can do now.

What you shouldn't do.

  • Put 3.0.0-next.* into production. Breaking changes kept landing all the way through to next.8 in the pre-release ($app/stores was removed in next.7, refreshAll arrived in next.8).
  • Treat remote functions as a stable API and build a team standard or a public library on top of it. There's no guarantee something like 2.61's .run() removal doesn't happen again — that's the definition of experimental.
  • Put off v3 prep on the theory of "I'll do it all at once when Svelte 6 lands." As shown above, the two majors are decoupled, so the thing you're waiting on differs.

If you're an adapter or library author there's more work ahead. createEntries is gone, adapters will be able to supply Vite plugins directly (#16206), and adapters are being reorganized around the Vite environment API. That ecosystem, more than app code, is the likelier practical bottleneck at v3's release.

Closing

The changelog for the SvelteKit 3.0 pre-release isn't flashy. And I think that's a good sign. This isn't the kind of major that demands relearning — a swapped-out router, a changed data-loading model — it's a major that pays off deprecation notices, raises the floor, and tightens defaults. You don't have to relearn runes, and you don't have to rewrite your load functions.

What you do need to get exactly right is expectations. Remote functions and component await becoming "official" is not an event that happens at SvelteKit 3 — the former has no timeline yet, and the latter is, per the docs, Svelte 6's job. The reading that lines up with the changelog is that v3 is the release laying the floor that future stands on (a rolldown-based Vite 8, Node 22, a cleaned-up API surface). Since the pre-release is still in progress, some of the details in this post may have changed by the stable release — this is written as a snapshot on the premise that it'll be checked again then.

References