Web Development

the framework that isn't there

On Svelte's compiler philosophy, SvelteKit's complex simplicity, and a small room in Stockholm where a community discovered it was real.

There’s a question that sits underneath every framework choice, usually unasked: what exactly am I shipping to the user? With most frameworks, the answer includes the framework itself - a runtime that interprets your intentions at the moment they need to become pixels on a screen. React ships a reconciler, a scheduler, a diffing algorithm. Vue ships a reactivity engine and a virtual DOM. These are sophisticated machines, and every user pays the cost of running them whether the page displays a blog post or a real-time dashboard.

Svelte begins from a different premise: the framework is a tool for the developer’s mind, and the user’s browser has no use for it. A .svelte file is a set of instructions to a compiler, and what emerges on the other side is vanilla JavaScript that manipulates the DOM directly - no intermediary, no runtime, no framework in the traditional sense. The thing you worked with during development is gone by the time anyone visits the page, and this turns out to be an inversion of what a framework is for, more than an optimization of an existing one.

starting from HTML

Most modern frameworks start from JavaScript and bring HTML along as a passenger. JSX looks like HTML but lives inside JavaScript, subject to JavaScript’s rules - single root elements, className instead of class, expressions in curly braces where attributes used to be. The markup serves the language.

Svelte inverts this. A .svelte file is valid HTML by default - you can paste raw markup into one and it works. The <script> tag adds logic, the <style> tag adds scoped CSS, and both are optional. The language serves the markup, which is a statement about what the web’s native medium actually is, and it gives the compiler a constrained, analyzable structure that JavaScript-first approaches structurally cannot replicate.

What this means in practice is that Svelte components tend to read the way the page will look. The structure of the code mirrors the structure of the output, and the cognitive distance between what you write and what the user sees is shorter than in any other framework I’ve worked with.

reactivity as language

The way Svelte thinks about state has shifted over its lifetime, and the shift is itself instructive. Svelte 3 took the position that reactivity should be invisible - you declare a variable, you assign to it, and the UI updates. No setState, no hooks, no API to learn. The compiler instruments the assignments behind the scenes, and the developer just writes JavaScript.

This was beautiful and it was also, in a specific way, dishonest. The magic only worked inside .svelte files. The moment you extracted logic into a plain .js module - a natural refactoring step - the reactivity broke because the compiler couldn’t see the assignments anymore. Code behaved differently depending on its file extension, which is the kind of subtlety that erodes trust over time.

Svelte 5’s runes ($state, $derived, $effect) trade a few characters of syntax for something more valuable: consistency. let count = $state(0) is more explicit than let count = 0, and it works identically everywhere - in components, in modules, in shared utilities. The reactivity travels with the code. Rich Harris called the distinction “magical, not magic” - the experience should feel empowering without being opaque, and the earlier version had crossed into opacity.

the kit

SvelteKit is where things get genuinely interesting, because it takes the compiler’s philosophy and extends it into application architecture with a set of opinions that sound simple and turn out to be deep.

File-based routing with the + prefix convention (+page.svelte, +layout.svelte, +page.server.js) solved a specific problem - colocating helper components with routes without them accidentally becoming routes themselves - and what it actually established was a contract. The + namespace says: these files participate in the framework’s orchestration. Everything else is yours.

The load function is the most considered piece of the architecture. It creates a formal separation between data preparation and rendering - data is ready before the component mounts, there are no loading spinners by default, no waterfalls unless you create them. The two-tier system, universal loads that run on both server and client and server loads that never touch the browser, places the serialization boundary at the resource level rather than the component level. You decide, per route, what needs server access and what can run anywhere.

And then there are form actions, which might be the most quietly radical feature in any modern framework. A <form> with an action works without JavaScript. The server processes the POST, returns a result, the page updates. Add use:enhance and the same form submits via fetch, updates the UI without a reload, handles errors gracefully - and the server-side code doesn’t change at all. Progressive enhancement here is the default architecture, and the JavaScript is purely additive.

stockholm

I was in Stockholm in November 2022, when the Svelte community gathered in person for the first time. Previous summits had been online - COVID had kept everyone distributed, collaborating through Discord channels and pull request threads on a framework without having shared a room. Stockholm was the first time many of these people saw each other’s faces outside of a video call.

The event was small by conference standards, which turned out to be the point. No expo hall, no vendor booths, no lanyard hierarchy. Rich Harris talked about the road to SvelteKit 1.0, which would ship a month later, and what struck me was the room itself, more than the technical details that were already available in the GitHub discussions. A few hundred people who had independently arrived at the same aesthetic conviction about how web development should feel, meeting for the first time and discovering that the community they’d built online was real in the way that only physical presence can confirm.

There’s something instructive about a framework whose first in-person gathering happened after years of the community already working together. The tool had shaped the community before the community had shaped itself, and what you saw in Stockholm was people recognizing a shared sensibility they’d each developed in isolation - an allergy to overhead, a respect for the platform, a preference for solutions that disappear into the work rather than announcing themselves.

complex simplicity

Rich Harris borrowed the phrase “transitional apps” from interior design - blending traditional and modern - to describe the rejection of the SPA versus MPA binary. A well-designed framework should let you server-render for speed and resilience, enhance with JavaScript for fluidity, and prerender where content is static, all within the same application, per page if needed.

What makes this a complex simplicity rather than just complexity is that each of these capabilities is exposed through the same small set of primitives. A page option (export const prerender = true) turns a route into static HTML at build time. Another (export const csr = false) strips all JavaScript from a page entirely. The load function handles both SSR and client-side navigation through the same code path. Forms work with and without JavaScript through the same markup. The surface area is small, and the depth is enormous.

This is the oddity of SvelteKit - that so much architectural power fits into so few concepts, and that the framework’s opinions consistently point toward the web platform’s native capabilities rather than away from them. It uses HTTP instead of abstracting it, enhances forms instead of replacing them, compiles for the browser instead of fighting it. And then, true to its nature, it disappears.

Pili
Pili
Pili
Pili
Pili
Pili
Pili
Pili
Pili
Pili
Pili
Pili
Pili
Pili
Pili
Pili
Pili
Pili
Pili
Pili
Pili
Pili
Pili
Pili
Pili
Next growing computation FORN