Svelte is a framework that has ruled the hearts of developers for years. This week saw the release of SvelteKit, a server-side renderering framework for Svelte that has been in the pipeline for quite some time. If you are curious about how SvelteKit is going to challange Next.js and Nuxt then this is the article for you.
1. SvelteKit 1.0
It’s 2016 and Rich Harris is one of the software developers at the New York Times. One of his duties is to prepare interactive data visualizations for articles. Due to the lack of better alternatives, Rich decides to create his own framework – Reactive.js. One of its biggest drawbacks is the huge JavaScript package sent to the client. During one of the local meet-ups, Rich listens to a presentation about compilers. The presentation inspires him so much that he devotes the entire Thanksgiving weekend to an intense hackathon. On the last evening of the weekend, Svelte 1.0 – a fully compiled JavaScript framework that sends minimal JavaScript to the client – hits the web.
Svelte is quickly gaining the hearts of developers. In State of JS 2021, Svelte ranks fourth in the “Usage” category for the second consecutive year. Looking at the “Retention” and “Interest” categories, one can’t help but get the impression that Svelte has that “something” that developers love. In these categories, Rich Harisses’s framework has taken one of the first two places for three consecutive years. We are still waiting for the results of State of JS 2022, but I’m betting dollars against nuts that Svelte will repeat its score this year.
Today, we won’t be talking about Svelte itself, but the project that Rich Harris has been working on for the past months. This week we finally got the first stable version of SvelteKit, a framework for rendering Svelte on the server. If you haven’t had any exposure to Svelte so far, you should leave this article and get busy learning this great framework I will try to describe it in the most accessible way possible for the average Frontend Developer.
File structure-based routing
Like Remix, Next.js or Nuxt, SvelteKit also has decided to base routing on a directory structure. From the start, we’re talking about a full suite of capabilities here – parameters ([slug]
), optional fragments ([[optional]]
), matching fragments of any length ([...rest]
), and even the ability to validate data types in a path with personalized matchers ([id=integer]
).
src/routes
├ blog
│ ├ [[author]] 👈 Optional parameter
│ │ ├ [slug] 👈 Dynamic slug
│ │ │ ├ +page.svelte 👈 This will match both /blog/tomasz-borowicz/angular-15
│ and /blog/angular-15
├ cites
│ ├ [id=integer] 👈 Type validator (note: this requires creating ParamMatcher)
│ │ ├ +page.svelte 👈 This will match both /cites/125
│ but not /cites/f8b0fc3e-6421-4722-b420
├ snippets
│ ├ [...filePath] 👈 Rest parameters
│ │ ├ +page.svelte 👈 This will match both /snippets/javascript/hoisting/index.js
│ and /snippets/index.js
SvelteKit page
Each page in SvelteKit consists of two files: +page.js
and +page.svelte
. In the first one, you should place the load()
function, which will be responsible for loading the data needed to render the page. In the second one, you should place the Svelte component, which will properly transform the received data into HTML.
// src/routes/blog/[id]/+page.ts
import type { getBlogPostById } from '$lib/rest.ts';
import type { PageLoad } from './$types';
export const load = (async ({ params }) => {
return await getBlogPostById(params.id);
}) satisfies PageLoad; // 👈 Note how SvelteKit uses brand new satisfies operator here
// src/routes/blog/[id]/+page.svelte
<script lang="ts">
import type { PageData } from './$types';
export let data: PageData;
</script>
<h2>{data.post.title}</h2>
<div>{data.post.content}</div>
In default mode, SvelteKit will render the first page on the server and then pass the rendering to the client. However, we can modify this behavior by changing the file extension to server.js
/server.svelte
. In such a situation, SvelteKit will make sure that such code is always executed on the server side.
// src/routes/blog/[id]/+page.server.ts
import * as db from '$lib/server/database';
import type { PageLoad } from './$types';
export const load = (async ({ params }) => {
return await db.getPostById(params.id); // 👈 Note you can acces server API here
}) satisfies PageLoad;
Using several variables exported from the component, we can also easily control the rendering strategy
// src/routes/about/+page.svelte
<script lang="ts">
export const prerender = true // render at build time
export const ssr = false // disable server rendering
export const csr = false // disable JavaScript hydration
</script>
<h2>About</h2>
<p>This is a very cool blog of a very cool company</p>
Layouts ans Errors
SvelteKit in its first version has functionalities that in other frameworks we waited for even until version 13 (yes, I’m looking at you Next.js 😈). Of course, we’re talking about layouts and error handling. At any level of the directory structure we can place +layout.svelte
or +error.svelte
files. Importantly, as with the pages, we can ensure that the code is executed only on the server by modifying the file extension.
// src/routes/blog/+layout.svelte
<script lang="ts">
import type { LayoutData } from './$types';
export let tabs = [ /*...*/];
</script>
<h2>Settings</h2>
<div class="submenu">
{#each tabs as tab}
<a href="{tab.href}">{tab.title}</a>
{/each}
</div>
<slot></slot> // 👈 All the pages in /blog will be rendered in place of this slot
// src/routes/blog/+error.svelte
<script>
import { page } from '$app/stores';
</script>
<h2>{$page.status}: {$page.error.message}</h2>
Forms handling
SvelteKit also offers a copied from Remix original solution when it comes to handling forms. In the +page.js
file we can place actions
const. It will be responsible for processing submitted forms.
// src/routes/login/+page.ts
import type { PageServerLoad, Actions } from './$types';
export const load = (async ({ cookies }) => {
const user = await db.getUserFromSession(cookies.get('sessionid'));
return { user };
}) satisfies PageServerLoad;
export const actions: Actions = {
login: async ({ cookies, request }) => {
const data = await request.formData();
const email = data.get('email');
const password = data.get('password');
const user = await db.getUser(email);
cookies.set('sessionid', await db.createSession(user));
return { success: true };
}
};
Then, in the +page.svelte
file, we can add the action
parameter to the form. As a result, when the form is submitted, our application will send a POST request to the server. Then the appropriate method from the actions object will be called. What’s so special about it? Well, thanks to the use of native browser mechanisms, this mechanism will work even after the user disables JavaScript in the browser!
// src/routes/login/+page.svelte
<script lang="ts">
import type { PageData, ActionData } from './$types';
export let form: ActionData;
</script>
<form method="POST" action="?/login">
<label>
Email
<input name="email" type="email">
</label>
<label>
Password
<input name="password" type="password">
</label>
<button>Log in</button>
<button formaction="?/register">Register</button>
</form>
{#if form?.success}
<p>Successfully logged in! Welcome back, {data.user.name}</p>
{/if}
Backend for Frontend
Since SvelteKit already supports POST requests when handling forms, maybe it could also handle other request types? Framework developers asked themselves a similar question. As a result, in SvelteKit we can define our own REST endpoints
// src/routes/api/random-number/+server.ts
import { error } from '@sveltejs/kit';
import { random } from 'lodash/es';
import type { RequestHandler } from './$types';
// You can call this endpoit with PATCH /api/random-number?min=5&max=8
export const PATCH = (({ url }) => {
const value = random(url.searchParams.get('min'), url.searchParams.get('min'), true);
return new Response(String(value));
}) satisfies RequestHandler;
Preloading
One very popular pattern to optimize JavaScript load time is to load only the code needed to render the current page. Unfortunately, this is done at the expense of fetching subsequent files when the user wants to navigate to another page. In such a scenario, the user is likely to experience a slight crunch. SvelteKit offers a cure for that too. If you will annotate the <a>
element, Svelte will start loading the necessary code and data as soon as the user’s cursor starts moving in its direction or as soon as the link appears on the screen.
<a data-sveltekit-preload-data="hover" href="/page-A">
This page code will load when cursor hovers over the link
</a>
<a data-sveltekit-preload-data="viewport" href="/page-B">
This page code will load when the link enters the viewport
</a>
SvelteKit Deployment
Rich Harris has long been a full-time employee of Vercel – the company that develops Next.js and makes money mainly from its cloud. Unsurprisingly, SvelteKit 1.0 is available for one-click deployment to Vercel’s cloud since day zero. Fortunately, it’s not a vendor lock-in and you can host SvelteKit on any Node.js environment.
Sources
https://svelte.dev/blog/announcing-sveltekit-1.0
2. Vite 4.0
Vite is one of the fastest-rising stars in our frontend community. Initially, it was a tool dedicated for building applications written in Vue. The revolution didn’t come until the release of Vite 2.0, when it became a framework-agnostic tool. The rumor about Vites speed spread quickly and attracted many developers. Vite became so popular that it very quickly cannibalized all of its competition (🪦 RIP Snowplow).
Last week we saw the release of Vite 4.0. This is a bit surprising, given that less than six months ago Vite 3.0 was released. However, it’s worth remembering that Vite uses semantic versioning. In such cases, the big number update is primarily related to breaking changes (and not necessarily new functionalities). The source of the breaking changes is primarily the switch from Rollup 2.0 to Rollup 3.0.
In the introduction to this section, I wrote that Vite has wiped out all its competition. This year, a new contender for the crown has emerged – Turbopack (written in Rust by the former creator of Webpack). For the time being, it is in the early alpha stage and is very buggy. But that hasn’t stopped its creators from bragging that it runs almost 4x faster than Vite. Evan You (creator of Vue and Vite) quickly started looking into these numbers. As it turned out, most of this advantage was due to using SWC instead of Babel and lightly adjusting benchmarks in favour of Turbopack. The dust after the “performance scandal” has settled a bit, and Vite 4.0 adds support for SWC. I’m curious how Vite and Turbopack compare now.
The most comments on Reddit under the post announcing Vite 4.0 were about recognizing the logos on one of the graphics in the post. I invite you to play this game too – how many of the logos in the graphic below are you able to recognize?