Table of Contents
status
dsq_thread_id
SvelteKit: A positive review
I’ve been using SvelteKit quite a bit recently and overall I’ve enjoyed it. Here’s my review.
Let’s start with what a web framework should provide
What should a modern frontend framework give you?
- A minimal server framework
- A way to manage state
- A build system that Just Works™
- An excellent developer experience
- A sane deployment story
SvelteKit hits all these bullet points.
A minimal server framework
Yes, you’re going to want a server, probably. Since Svelte (not SvelteKit) is a frontend UI framework, it’s worth calling this out. Over time the frontend ecosystem realized that you’re going to want a server often enough that your “frontend” framework might as well handle it for you.
This may have started with Next.js, not sure, but the fact of the matter is that rendering frontend frameworks on the server was a pain in the ass for a while. It still is if you do it yourself. Thankfully the ecosystem has realized that no one wants to configure this themselves, so all the “big players” in the frontend space have an answer to this:
- React: Next.js
- Svelte: SvelteKit
- Vue: Nuxt
- Solid: SolidStart
It’s become table stakes for frontend frameworks to have a built-in answer to questions like?
- How do I handle routing?
- How do I render static pages for SEO juice and better UX?
The SvelteKit (SK) approach
Using SvelteKit you define the routes in your app using the filesystem, similar to (but not the same as) you would using plain HTML or PHP:
/
:index.html
/settings
:/settings/index.html
/settings/user/change-password
:/settings/user/change-password/index.html
- etc…
SK allows you to create both server and client routes, or even both at the same path, so they had to come up with some specific semantics. Unfortunately, those semantics are specific to SK and not necessarily intuitive, but it’s not much to learn.
Here’s an example from one of my projects. The
src/routes
directory contains the apps routes.src/routes
├── +layout.svelte
├── +layout.ts
├── +page.svelte # The index page (/)
├── api
│ └── v1
│ └── plugin
│ └── resolve
│ └── +server.ts # /api/v1/pugin/resolve
├── pwa
│ └── manifest.webmanifest
│ └── +server.ts # /pwa/manifest.webmanifest
└── settings
├── +layout.server.ts
├── +layout.svelte
├── +page.svelte # /settings
└── plugins
├── +page.svelte # /settings/plugins
└── [pluginUrl]
├── +page.svelte # /settings/plugins/<url> (a dynamic value)
└── +page.ts
Without getting into the SK semantics too much, some things to note:
- You can create API routes that expect JSON and return JSON
- You can create routes for files that are actually dynamic, such as
/pwa/manifest.webmanifest
which return dynamic data while appearing as a static file to the outside world (here’s the live URL).
- It probably goes without saying, but you can generate HTML pages using svelte syntax, which is what all the
+page.svelte
files do.
Overall I have yet to find myself restricted by this model, although some of the semantics are not obvious, such as creating a
+server.ts
route in order to generate a file from the browser’s perspective.A way to manage state
This is a Svelte feature, and not specific to SK. However, Svelte’s built-in state management works seamlessly with the full-stack framework which is a big win.
I never liked React Hooks…
I started using React well before “Hooks” became the de facto way to manage state, so I’ve used several other state management solutions in the past. My favorite of the lot was MobX. The core idea was this:
- What if you could define plain objects that would cause your app to rerender when their properties changed?
- What if you could do it without sacrificing performance?
Why bring up MobX? Because Svelte’s built-in “store” abstraction for state management is essentially the same thing, and it’s quite nice.
You define mutable objects. You reference them within your components. When those objects change your UI re-renders.
Svelte stores roughly Just Work™ as one would hope, including during server-side rendering.
A build system that Just Work™
So far the SK build system has indeed just worked. I haven’t had to tweak it much. Thumbs up 👍. That being said, there’s a bit more to add here:
- SvelteKit has “add-ons” which are little automations that add something to your existing configuration
- Example: You want to use Tailwind with SvelteKit. Use the
svelte-add
package to add it to an existing project:npx svelte-add@latest tailwindcss
- The repo:
- Aside: In true JS-ecosystem fashion, the recommended way of using the tool is to start over (c’mon, really?) but in my experience the command works with existing projects too.
- Sometimes a build will fail because some package is incompatible with server side rendering
- In SK every individual route can be configured to pre-render at build time, render on the server at runtime, or render in the client at runtime.
- This configurability is quite flexible and makes it easy to prevent certain pages from rendering on the server.
- JS graphics libraries have a particularly hard time rendering on the server, which is just fine. Not everything has to run in every JS environment.
- SK is unfortunately a bit aggressive about getting you to render on the server. It has the feel of some kind of crusade. However, the framework also provides options to disable this so it ends up not being an issue
An excellent developer experience
If not excellent, it’s good enough.
SK has room for improvement here. Out of the box their dev tooling definitely works, and it works well enough. However, it’s still inferior to Next.js. With SK, your components often fully rerender, demolishing any state you had. It’s also not very fast. These issues aren’t deal-breakers though and can be improved over time (hopefully).
A sane deployment story
Check. SvelteKit has many means of deployment thanks to their adapter system. An adapter will take a built SK app and serve it, somehow. This is very flexible because you can write your own adapters if needed.
Most likely you won’t need to.
To get a quick side project out the door you can deploy straight to Vercel/Netlify without zero additional configuration.
If your website is static you can use the static adapter to generate a fully static site (similar to next.js export functionality).
If you’re ready to go to prod and want a server-full environment use the node adapater.
There are even existing adapters for alt runtimes like deno and Bun. I have not tried these.
All in all, the deployment story was better than expected. Thumbs up 👍.
Gotchas
- Only one
<slot />
, no named slots for layouts. This is very unfortunate, since it breaks existing Svelte (not SvelteKit) behavior. Not a huge issue, but enough little things like this and you end up fighting the framework.
- I could not get a docker build working on
node:18-alpine
. I’m still not sure why, but you will likely have to settle fornode:18-slim
to get your deployment to build. My final docker image was about 500mb, which is (hilariously) fine by node.js standards. (Whenever I deploy anything, I miss Go)
Projects
The projects I’ve built with SvelteKit so far:
- ChineseExamples.com
- Frontend + Node.js backend
- Full server-side rendering, minimal AJAX
- Basically an “old school” website—A server process hits a database and spits out fully-rendered HTML.
A website for finding example sentences in Chinese. Useful for advanced Chinese language learners.
- Prompta
- Fully static
- Web app + desktop app via Tauri
- A single-page web app (SPA)
A UI for interacting with ChatGPT. There are a ton of these out there. Mine’s (subjectively) better.
Conclusion
I’ve quite liked developing with SvelteKit and I recommend it. It certainly felt more appealing than developing with Next.js, although Next recently (as of this writing) introduced routing features with layouts, similar to SvelteKit, which will likely improve the experience.
Svelte itself is a great library for rapid prototyping and iteration. I have complaints about it, which i’ve left out of this essay, but all things considered I enjoy using it.