I have been working on a big project for many months called Tea Share and in the process, I switched to SvelteKit from Next.js.
Now you might wonder…
What is Tea Share?
Tea Share is a social media app similar to Instagram or Twitter for the Web & Android.
It is highly interactive so therefore an obvious candidate for a fullstack framework and it also needs to be fast + accessible.
Why did I make the switch?
Well, I have a very stupid reason for it, highly interactive fast apps.
Why did I call it stupid? Because the statement “Moving from React to Svelte because I want highly interactive apps.” is like saying “I am moving from Java to Kotlin because I want to write a JVM app.”. I moved from React to Svelte because I want to have a highly interactive app without a massive client-side JavaScript bundle.
React and Svelte are both awesome frameworks for making a highly interactive blazingly fast UI. But the RSC (React Server Component) paradigm shift made my really paranoid to serve JavaScript to users because React itself is 51kb! + The Next.js addition for client-side routing and route-prefetching with many other features increase it by another 21kb.
Eventually, I couldn’t handle the app with being so paranoid on JavaScript. So then, I switched to SvelteKit. I couldn’t believe how low the bundle size was after I used it! The app with Next.js at barely user friendly state (Almost everything server-side) was 109kb (GZipped) but the app built with SvelteKit that now focuses on UX as well as performance, even though it was now full hydration && many features were now also client-side, the apps bundle size was ONLY 48kb (GZipped)
Progressively enhanced JavaScript apps are the past future of web development
This is one of the main reasons I really like SvelteKit. It puts a lot of effort in helping use non-JS needed features like
<form>
and then adding easy APIs to progressively enhance it with client-side JavaScript to improve the UX.
I remember when I started learning HTML, CSS & JS, I also tried doing progressive enhancement, and it was painful.
For reference, here is vanilla JS & HTML vs SvelteKit
<html lang="en">
<head>...<head>
<body>
<form action="/some-api" method="POST" id="login-form">
...
</form>
</body>
</html>
Then in the JS
document.getElementById("login-form").onsubmit = async e => {
e.preventDefault();
const formData = new FormData(e.target);
await fetch("https://.../some-api", {
method: "POST",
formData,
});
};
Whatever I am showing here is just a small example, imagine this in a big codebase.
But SvelteKit? Its as simple as it gets.
<script lang="ts">
// Only around 1kb DECOMPRESSED!
import { enhance } from "$app/forms";
</script>
<!-- form action progressive enhancement -->
<form action="?/login" method="POST" use:enhance>...</form>
Thats it! Super simple an straight-forward.
Ok, progressive enhancement is nice, but why do I even care?
But seriously, having JS to enhance is better than empowering JS to make the app collapse.
SvelteKit is nice(r) to work with
To me, writing UIs in Svelte were nicer than JSX. The JavaScript (with TypeScript support) is minimal & feels like just vanilla JS without the pain points.
The UI code when converted from React -> Svelte decreased by 33% !
Svelte also has a lot of shorthands that make writing UIs easier. Here are only some of them in one snippet:
<script lang="ts">
import { onMount } from "svelte";
import { writable } from "svelte/store";
import Component from "./Component.svelte";
// built in state management
const store = writable(null);
const stuff = 23;
const things = [];
// reactivity
let count = 0;
// computed state
$: doubled = count * 2;
$: {
// side effects
console.log(`Count is ${count}`);
}
// async mount functions
onMount(async () => {
await import("...");
});
</script>
<!-- managed subscription -->
<h1>{$store}</h1>
<!-- props shorthand -->
<Component {stuff} />
<!-- non-keyed fast loops -->
{#each things as thing}
<div />
{/each}
<!-- readable in-template promise handling -->
{#await Promise.resolve("hi")}
<div aria-busy>Wait</div>
{:then data}
<article>{data}</article>
{:catch}
<p>uhh</p>
{/await}
So much stuff done in one tiny template file. Now thats a HUGE productivity boost.
Performance
Like I said in the “Why I made the switch” section, Svelte is already faster than React. (And thats huge for me)
I care about performance. I tried a lot on trying to get React going fast & I always came to a 200kb bundle size + the runtime performance were good enough in Next 12. (Next 13 is a different story)
But Svelte CRUSHED it. And not only the client-side bundle size that I mentioned before, but also the server-side performance. The server bundle went down from 6MB -> 418kb.
Response times became 2.3x faster, client-side bundles sat at 48kb & the bundle size increase is very minimal. (only like 3kb for 3 components).
Svelte’s compiler (especially with version 4) produces highly optimized JavaScript & it runs incredibly fast. The hydration overhead is very small. And speaking of hydration, since Svelte compiles to Vanilla JavaScript and does not have a Virtual DOM / Component Tree, its actually impossible to get hydration errors in it. In React, before server components (RSCs don’t hydrate so no problem using them), there were a lot of problems with hydration when showing a formatted time. If there was a post published 10s ago, on the server it would generate ”10s ago”, but by the time the client receives the generated HTML, obviously the time would be now around 11-12s ago, but since the server generated HTML still has 10s, it throws a hydration error because we need both server & client data to be idempotent to hydrate the page correctly. But in Svelte, it doesn’t matter. Since there is no component tree, the hydration process in SvelteKit is only attaching event-listeners to stuff, & there was no need for extra overhead of a VDOM.
Things I hate about Svelte
Svelte despite its greatness still has some flaws.
The templating language
The templating language of Svelte is kind of annoying tbh. Its definitely concise, readable & gets the job done, but I really hate it because of many annoyances. For example, lets take React JSX vs Svelte.
export default async function Page() {
const items = await getItems();
return items.map(item =>
<ItemCard
key={item.id}
special={items.special.property as SpecialProperty}
{...item}
/>
);
}
Notice that type assertion? Now lets look at Svelte
// +page.server.ts
export const load = async () => ({ items: await getItems() });
<!-- +page.svelte -->
<script lang="ts">
export let data;
</script>
{#each data.items as item}
<ItemCard
special={items.special.property as SpecialProperty}
{...item}
/>
{/each}
The code looks fine & simple, but actually, this is wrong. Svelte’s templating language doesn’t have support for type assertions. You might think its ok, but its also really annoying to manage. And this is not just type assertions, many TS features are NOT POSSIBLE in Svelte.
Why +
This might be me nitpicking but why do the framework specific files start with a ”+”. Its just not natural to write +page.svelte, its much better IMO to write page.svelte.
Exports lol
Here are two files, stuff.ts && +page.svelte
// stuff
import type { Garbage } from "./+page.svelte";
<!-- +page.svelte -->
<script lang="ts">
export interface Garbage { ... }
function processGarbage(garbage: Garbage): Garbage {
garbage.dispose();
}
</script>
...
This doesn’t work because Svelte files are just components, and whatever you export is a prop. The export let
syntax is used for props, but for others? Doesn’t actually export anything.
But now for the final question…
Will I leave React for Svelte for all my apps?
NO.
I still love using React, its still my favourite framework over Svelte. But for my apps, it really depends. If I am building a really big project, Svelte is the new way for me. Its accessibility and performance is all I need for a big production-grade app (Like Tea Share) But React is evolving rapidly and I expect more improvements in performance in the future. If I was given the choice of React vs Svelte for a non-performance critical app, I would choose React any day over Svelte.