What's The Deal With SvelteKit 2?

| Published: January 06, 2024

| Updated: February 03, 2024

SvelteKit 2 was released on the 14th December 2023, which was somewhat of a surprise to the common Svelte developer. With minimal hype leading up to the launch, a simple post reply from Svelte’s creator Rich Harris on X sent Reddit into theorizing.

The Rich Harris post teasing of a SvelteKit 2 release. He was right!

The Tweet from Rich Harris mentioning the release of SvelteKit 2

The people began to speculate on which features SvelteKit 2 would bring. Many citing some of the popular discussion points on GitHub.

This article will discuss the main changes of Sveltekit v2 and how to implement some of the new features. We’ll also mention some of the features the Sveltekit community has been longing for that didn’t make it into the release. 👀

Skip to FAQ’s

What’s new in SvelteKit 2

In all honesty, SvelteKit 2 brought few major changes to the table over it’s predecessor. There are a couple breaking API changes, but the way you develop an app with SvelteKit hasn’t changed.

The official release statement from the Svelte team only mentions 1 new feature, and then talks about other community efforts and a poll.

SvelteKit was voted as the framwork most likely to be used for new apps!

The @flaviocopes poll

In the SvelteKit 2 Migration guide, we go far more in depth about some of the main changes in SvelteKit v2, so we’ll get to them.

But first, let’s talk about the big new feature first in the main announcement.

Shallow Routing

Shallow routing is a fairly advanced feature that allows the user to create history entries without navigating. In other words, we can decouple page renders from navigation.

Perhaps understanding this theoretically is rather useless without going through some of the use cases, and how it works to improve UX.


When we decouple the URL from the location history, we can do all sorts of things in the app without having to change the page we’re on. And more to that, we can ‘artificially’ create history events which the user can navigate back and forward to using their browser’s buttons.

The pushState Function

The most common example used to explain this is the idea of pushing a history event when a modal is opened on the page.

The page (URL) remains the same, however there is a clear distinction where the user perceives something new. Now, if the user presses back it will remain on the same page, however the modal will disappear. Traditionally, this would have taken the client back to the previous url, rather than the previous ‘state’ of the same page.

Pulling off this example is easy-peasy.

First, import pushState from the $app/navigation:

import { pushState } from '$app/navigation';

Then whenever you want to add a history event, call pushState(relativeUrl, newPageState), where realtiveUrl is the URL to push to the history events ("" to stay on current page), and newPageState is an object where you can modify page store variables.

function showModal() {
    pushState('', {
        showModal: true

So, the example used in the SvelteKit official docs is:

<script lang="ts">
    import { pushState } from '$app/navigation';
    import { page } from '$app/stores';
    import Modal from './Modal.svelte';

    function showModal() {
        pushState('', {
            showModal: true,

{#if $page.state.showModal}
    <Modal close={() => history.back()} />

Which means that to close the modal, we just go back one step in the browser history entries.


The replaceState Function

If we want to replace the current page (URL) without creating a history entry, we would use the replaceState function. With pushState, we’re artificially creating a history event, and with replaceState, we’re artificially removing a history event.

This means that if you were on website.com/page-1 and clicked to enter website.com/page-2, where some button clicked called a replaceState to take you to website.com/page-3, and you clicked back once - it would take you to the originial website.com/page-1.

Effectively removing the website.com/page-2 history entry when you called relaceState('page-2').

To visualize this, we’re doing this: page-1 -> (click) -> page-2 -> (click) -> page-3 -> (go back) -> page-1

Inside your Svelte markup, use replaceState shallow routing in your anchor tags by adding the data-sveltekit-replacestate attribute, like this: <a data-sveltekit-replacestate href="/path">Path</a>

More Use Cases for Shallow Routing

Pop-up views

Pop-up views allow you to render entirely new routes inside of a current page without needing to navigate or change the URL.


For example, you can open up a modal on a page, and render another page inside of that modal, without changing creating any history events… Or if you did want to change the history to include that, then you can! All whilst keeping the original page as the backdrop.

Huntabyte has a nice short video walking you through the code to perform a more complex function like this:

I wrote an extensive article on Shallow Routing if you’d like to learn more about it!

No Need to throw

In SvelteKit v2, you don’t need to throw for your SveletKit redirect and error functions.


Traditionally, if you needed to redirect a user from the server, you would have to throw it:

async function checkIfLoggedIn(event) {
    const session = await event.locals.auth.validateSession();
    if (!session) {
        throw redirect(401, '/login');
    return session

In SvelteKit v2, you can remove the keyword throw and call the function directly, so for the example above, redirect(401, '/login') is sufficient:

    if (!session) {
        redirect(401, '/login');

The same applies for errors.

Perhaps you have an error, and want to stop execution and throw the error details, then you just need to call error(code, message),

For example:

Old Way

if (!requiredValue) {
	throw error(500, 'You have made a boo boo')

✔️ New Way

if (!requiredValue) {
	error(500, 'You have made a boo boo')

No More Auto await in load Functions

With SvelteKit’s ability to stream promises from load functions to the client (which is awesome btw), version 1’s method of handling this was to automatically await fetching calls if the value was returned from the load function.

And if you wanted to pass the promise to the clients, you’d pass the function call, rather than the resolved value.

However, to remove some of the non-standard syntax, v2 requires you to await promises in load functions.

This is an example of how to await for fetched data in load functions now, vs how it was done before in v1:

Old Way

export async function load(event) {
	const response = fetch(url)
	const result = response.json()

	return {result}

✔️ New Way

export async function load(event) {
	const response = await fetch(url)
	const result = response.json()

	return {result}

So, just chuck an await before you fetch things in load functions. Pretty standard.

Don’t goto other websites like that!

The goto function you use to programmatically navigate inside your .svelte files doesn’t accept external URL’s anymore, sorry!

Back to the golden days of Javascript, where you use window.location = url to navigate to a new page.


❗ Reminder: goto will still work with relative (your website’s) URL’s - this is just for going to other websites.

When they’ve got your app, who needs to go to other websites anyway 😤?

Resolve this 🍑

In v2, resolvePath becomes resolveRoute, with a nicer API which includes the generated base. The new resolveRoute now includes base, so you don’t need to import it to get the full path.

Old Way

import { resolvePath } from '@sveltejs/kit';
import { base } from '$app/paths';

const path = base + resolvePath('/blog/[slug]', { slug: 'slug-name' });

✔️ New Way

import { resolveRoute } from '$app/paths';

const path = resolveRoute('/blog/[slug]', { slug: 'slug-name' });

Add a path to the cookies


When you Set-Cookie without setting a path, the browser sets it for only the parent of where it’s being called. If you’ve set cookies like this before expecting the cookies to apply to the entire domain, you’ve probably had a bad time trying to get to the bottom of it.

That’s why Sveltekit 2 now requires you include a path parameter on the the following cookies methods:

  • cookies.set(...)
  • cookies.serialize(...), and
  • cookies.delete(...)

Old Way

cookies.set(name, value);

✔️ New Way

cookies.set(name, value, { path: '/' });

To apply the cookies to the domain, you will set path: '/', but at least now the compiler will yell at you if you forget to set it.

A+ for damage control v2!

What’s NOT In SvelteKit 2?

Didn’t see a feature you hoped for on the list?


Remember, this was not an exhaustive list of the the new features and changes brought in SvelteKit 2. There’s a bunch of security updates and tech-debt that has changed under the hood also, e.g. moving to Vite 5.

There are still a bunch of features that weren’t included in the release that many Svelte enthusiasts were hopefully for.

The main features I’ve seen people looking forward to with SvelteKit is:

  • websockets, and
  • named slots in layouts

…which neither made into the v2 release.

I’ve been working on some websocket-heavy apps recently and would love to see them included natively into SvelteKit. I’m certainly not alone with this specific desire, however an official websockets integration was not included in the v2 changelog.

Whatever it is that you’d like to see part of SvelteKit, I encourage you to get active in discussions. This can be as simple as going through the SvelteKit issues page on GitHub and thumbs-uping 👍 changes you’d like to see. Or even making pull requests if you have the chops for it!

Minor Releases are Not Always Minor

It’s worth mentioning how regularly the Svelte team are pushing meaningful updates to Sveltekit, outside of the ‘major’ releases.

For example, the sveltejs/enhanced-img experimental package was released on the 12th November 2023 - outside of the main SvelteKit 2 announcement. The package was a big leap in closing the gap in the framework wars, and to better better deliver on the SvelteKit mission.

The enchanced-img package was a way to serve reformatted, resized and optimized images for webpages, akin to the next/image package in NextJS.

If something you are hoping for didn’t get released in SvelteKit v2 and it has wide support in the forums, there’s every chance it’s in the short-term roadmap. You might not have to wait for the SvelteKit v3 release in December 2024 (speculation given that v2 was released on the 1 year anniversary of v1😋).

Migrating to v2

For the best and most comprehensive tutorial on how to migrate to v2, follow the Migration Guide for SvelteKit v2 for step-by-step instructions.

Thankfully the Svelte team have put together a migration tool for updating or highlighting all instances in your app where there are breaking API changes.


A few caveats you should know before migrating to SvelteKit 2:

  1. SvelteKit 2 requires Svelte v4, so before migrating to Sveltekit 2, make sure your app is using Svelte 4. If you need to migrate to Svelte 4, follow this migration guide.
  2. It’s recommended that you update your SvelteKit version to the latest SvelteKit 1 version before migrating to SvelteKit 2. This will ensure the deprecations get highlighted.
  3. Sveltekit 2 requires Node 18.13, or higher.

Once you ensure your app and environment is compliant with the above 3 pre-requisites, run npx svelte-migrate@latest sveltekit-2 in your terminal and the tool will make some of the changes automatically.


This was a cool surprise, and I’m excited to implement some of the shallow-routing features into my apps.

With the release Svelte 5 around the corner, we can expect to see lots of changes and new faces joining the Svelte force. It’s exciting times being on this side of the fence! 💪

The removal of the throw keyword for redirects and errors is a simple syntax upgrade, and I’m also thankful for some of the compiler strictness changes.

Overall, this is a step in the right direction for SvelteKit, and the core team absolutely rocks for gifting this to us. We really are unworthy 🙏


Thanks for reading ❤️

Here are some other articles I think you might like!