Every Web Project Is Seven Decisions: A Layer-by-Layer Guide to the Modern Web Stack
Vite or Webpack? Next.js or SvelteKit? Tailwind or CSS Modules? They are not competing answers to the same question. They are answers to…
Every Web Project Is Seven Decisions: A Layer-by-Layer Guide to the Modern Web Stack
Vite or Webpack? Next.js or SvelteKit? Tailwind or CSS Modules? They are not competing answers to the same question. They are answers to seven different questions.

Intro
If you have tried to build a project recently, the sheer volume of choices in the modern web stack can look overwhelming. But let’s be honest: in the age of AI, that is no longer a blocker. You do not need to spend a weekend researching bundlers or frameworks. You can write a single prompt and have an AI spin up a fully functioning stack in a minute or two.
So if we can outsource the setup to AI in seconds, why write a guide explaining the layers?
Because if you are someone who wants to understand a little more deeply why those choices were made, “it just works” is not enough. You want to know what happens when you pick a meta-framework, and what trade-offs you are implicitly accepting. This article is not a time-saving research shortcut; the AI has already solved that. This article is for satisfying the developer curiosity to understand the map.
A web stack is not a pile of competing tools. It is a stack in the literal sense: seven layers, each one sitting on the one below it, each one answering a different question. Once you draw the map, every tool snaps into exactly one place, and the decision at each layer becomes clear.
This article is that map. I will walk through all seven layers from the bottom up, compare the real options at each one, and tell you what I picked for my own project and why. By the end you will be able to look at any new tool that launches next week and know immediately which layer it lives in and what it is actually competing with.
Here is the whole thing at a glance:
┌───────────────────────────────────┐
│ Layer 7: Animation & Motion │ ← interaction polish
│ Layer 6: Styling │ ← how components look
│ Layer 5: UI Framework │ ← how you write components
│ Layer 4: Meta-Framework │ ← routing, SSR/SSG, data loading
│ Layer 3: Build Tools & Bundlers │ ← transforms source → browser-ready code
│ Layer 2: Server Framework │ ← handles HTTP, APIs, business logic
│ Layer 1: Runtime │ ← the engine everything runs on
└───────────────────────────────────┘
Not every project needs every layer. A static site might skip Layer 2 entirely. A backend API skips Layers 5 through 7. But understanding the full stack shows you what you are choosing, and more importantly, what you are implicitly accepting when a framework chooses for you.
Let’s start at the bottom.
Layer 1: The engine nobody thinks about
The runtime is the engine that executes JavaScript and TypeScript on the server. It runs your build tools, powers your dev server, and serves your app in production. Everything else in this article sits on top of it.
For fifteen years this was not a decision at all. The runtime was Node.js, period. Now there are three serious options.

Node.js is the default for a reason. Every library assumes it, every hosting platform supports it, and it is battle-tested in production everywhere. It is also the slowest of the three and still needs a transpilation step for TypeScript.
Bun is the young challenger, written in Zig on top of JavaScriptCore instead of V8. Its pitch is raw speed: startup is 3 to 5 times faster, and package installs are dramatically quicker. For most projects it works as a drop-in Node replacement, though complex native modules can still hit compatibility gaps. If you maintain a large monorepo or care about CI pipeline speed, Bun is worth a serious look.
Deno comes from Node’s original creator and fixes Node’s design regrets: security-first with a permissions model, native TypeScript, web-standard APIs. It is stable and pleasant, but adoption stayed smaller, and it makes the most sense if you are deploying on Deno Deploy.
What I picked: Node.js. Boring answer, deliberate choice. Maximum compatibility with the rest of my stack and with Netlify. Bun would have worked as a drop-in for faster installs, and nothing in the layers above would change if I swapped it in later. That is the nice thing about the bottom layer: it is the easiest one to revisit.
Layer 2: Do you even need a backend framework?
One layer up sits the server framework: the thing that handles HTTP requests, API routes, middleware, and business logic. Express is the name everyone knows, but the field has changed more in the last four years than in the previous ten.
Here is the field today:

The pattern becomes obvious when you sort by generation:
Generation 1 (2010-2015): Express, Koa, Hapi
Callback/middleware patterns, Node.js only
Generation 2 (2016-2021): Fastify, NestJS
Speed + structure, still Node.js
Generation 3 (2022-now): Hono, Elysia
Multi-runtime, edge-native, TypeScript-first
Express is the jQuery of backend JavaScript. It is everywhere, everyone understands it, and it is showing its age. Callback-based middleware, no built-in TypeScript, no schema validation. New projects pick it because everyone knows it, not because it is the best tool. Fastify is what Express should have become: schema-based validation, a real plugin architecture, two to three times the throughput, and Express-compatible enough to migrate to.
Hono is the one I find most interesting. It is about 14KB, TypeScript-first, and runs on literally any runtime: Node, Bun, Deno, Cloudflare Workers, Lambda. If you are building for the edge or want to keep your deployment options open, Hono is the modern answer. Elysia plays the same game but goes all-in on Bun, squeezing out the highest throughput of anything on this list. And NestJS is the enterprise choice, with Angular-style decorators and dependency injection that feel instantly familiar to anyone coming from Java or Spring.
But here is the question that matters more than which framework: do you need this layer at all?
Meta-frameworks (Layer 4, coming up) ship with built-in server capabilities. Next.js has API routes, SvelteKit has server endpoints, Nuxt has server routes. For a lot of projects, those are enough:
Meta-framework routes are fine for: You need a dedicated server framework for:
- Form submissions - Complex business logic
- Email capture - Microservices
- Simple CRUD APIs - Real-time (WebSockets)
- Basic authentication - One API serving multiple frontends
What I picked: nothing. My form submission and email capture are a couple of endpoints, and SvelteKit’s built-in server routes handle them. If the project ever grows a real backend, Hono or Fastify would be the natural addition. Skipping a layer you do not need is a feature of understanding the stack, not a shortcut.
Layer 3: The tool you talk to every day without noticing
Every time you run npm run dev or npm run build, you are talking to Layer 3. Build tools sit between your source code and the browser, transforming TypeScript, JSX, Svelte files, and CSS imports into optimized files a browser can actually run.
This layer went through a revolution recently, and the revolution has a one-line summary: stop bundling during development.
Generation 1 (2012-2019): Webpack, Rollup, Parcel
Bundle everything, JS-based, slow on large projects
Generation 2 (2020-now): Vite, esbuild, Turbopack, Rspack
Native ESM in dev, Rust/Go for speed, 10-100x faster

Webpack dominated for a decade, and its configuration complexity became legendary in the process. It still powers enormous codebases, but new projects rarely choose it.
Vite is the current default, and it earned that position with one key insight: during development, do not bundle at all. Serve source files as native ES modules, let the browser import them directly, and only transform what is requested. The result is a dev server that starts in about 100 milliseconds whether your project has fifty files or five thousand. For production it hands off to Rollup, the bundler that pioneered tree-shaking. SvelteKit, Nuxt, Astro, and Remix all build on Vite, which tells you where the ecosystem has consolidated.
esbuild is the Go-based transformer doing the heavy lifting inside Vite, 10 to 100 times faster than Webpack at raw transformation. You will probably never configure it directly. Turbopack is Vercel’s Rust-based Webpack successor, currently exclusive to Next.js. Rspack is the pragmatic one: Rust speed with Webpack-compatible configuration, built as a migration path for big Webpack projects that want speed without a rewrite.
What I picked: Vite. Honestly, I did not pick it. SvelteKit picked it for me, and that is the most important lesson of this layer: in 2026, your meta-framework usually decides your bundler. You choose this layer indirectly.
Layer 4: The decision that makes the other decisions
This is the big one. The meta-framework is the orchestration layer: routing, server-side rendering, static generation, data loading, deployment. It is the thing you actually npm create when you start a project, and it reaches down to pick your bundler and up to dictate your UI framework.
If you only think hard about one layer, make it this one.

The names matter less than the three architectures hiding underneath them:
Ship the full runtime. Next.js, Nuxt, and Remix send the framework to the browser along with your code. Every user downloads React or Vue itself, then your application on top. Consistent and predictable, but you start with a baseline of framework weight before writing a feature.
Compile the framework away. SvelteKit takes the opposite approach: Svelte components become plain vanilla JavaScript at build time, and no framework ships to the browser at all. The framework exists only on your machine.
Islands. Astro and Fresh render the page as static HTML and ship JavaScript only for the specific components that need interactivity. A page that is 90 percent static gets 90 percent less JavaScript. For content-heavy sites this is the theoretically optimal architecture, and Astro deserves special mention because it is framework-agnostic: you can write your islands in React, Svelte, Vue, or Solid, mixed in one project.
What I picked: SvelteKit. And I will be honest about the road not taken: for a site that is 80 percent static content, Astro was arguably the more “correct” choice on pure architecture. I went with SvelteKit because my interactive parts (the timeline, the assessment flow) are stateful enough that I wanted one coherent app model rather than scattered islands, the Netlify adapter is first-class, and the compile-away approach gets me most of Astro’s weight savings anyway. When two options are this close, ergonomics win.
Layer 5: How you actually write components
The UI framework defines how you author components and manage state. Here is the twist that most comparison articles miss: by the time you reach this layer, the decision is usually already made. Pick SvelteKit and you write Svelte. Pick Next.js and you write React. Astro is the only mainstream escape hatch that lets you mix.
So why understand this layer at all? Because it should inform your Layer 4 choice, not follow from it.

The numbers are interesting, but the mental models are where these frameworks really differ. Here is the same counter with derived state in all four:
// React: state is a function call, re-renders the whole component
const [count, setCount] = useState(0);
const doubled = useMemo(() => count * 2, [count]);
// Svelte 5: state is a variable, the compiler tracks dependencies
let count = $state(0);
let doubled = $derived(count * 2);
// Vue 3: state is wrapped in ref(), the template tracks dependencies
const count = ref(0);
const doubled = computed(() => count.value * 2);
// Solid: state is a signal, fine-grained updates, no re-renders
const [count, setCount] = createSignal(0);
const doubled = () => count() * 2;
React asks you to learn its rules: re-render cycles, dependency arrays, memoization, the rules of hooks. In exchange you get the largest ecosystem and job market in front-end history. Vue softens those edges with the Composition API and is enormously popular outside the US. Solid keeps React’s look but replaces the virtual DOM with fine-grained signals, so updates touch exactly the DOM nodes that changed. And Svelte, especially since version 5 introduced runes, lets you write nearly plain JavaScript: a variable marked $state is reactive, and the compiler figures out the rest at build time.
What I picked: Svelte 5. For a content-heavy site, shipping a ~2KB compiled output instead of a ~44KB runtime is a real difference users feel on slow connections. The syntax is also simply less code: no hooks, no dependency arrays, no memoization rituals. The trade-off is a smaller ecosystem and a niche job market, which matters for hiring on a team but not for a solo project.
Layer 6: Where the religious wars happen
Styling is the layer with the widest spread of philosophy, and the only one where developers get genuinely angry at each other. The options span a full spectrum from total control to total abstraction:
Full control <-------------------------------------------------------------> Full abstraction
Plain CSS → CSS Modules → Open Props → Vanilla Extract → Tailwind/UnoCSS → styled-components
↑ ↑
Write every rule yourself Never write a CSS rule

Tailwind dominates new projects, and it is still controversial. You write utility classes like text-lg bg-blue-500 p-4 directly in your markup instead of authoring CSS files. Critics call it inline styles with extra steps. In practice it eliminates three chronic problems at once: naming things, dead CSS accumulating forever, and specificity wars. Version 4 became a Vite plugin, so even the PostCSS configuration is gone.
CSS Modules are the sane middle ground: write normal CSS, get automatically scoped class names, never worry about leaks. styled-components pioneered CSS-in-JS but is falling out of favor because of its runtime cost; the industry has moved decisively toward build-time solutions like Vanilla Extract, which gives you fully typed styles in .css.ts files with zero runtime. UnoCSS is the efficiency play: Tailwind-compatible class names, but it generates only the CSS you actually use, on demand.
One footnote worth knowing if you go the Svelte route: Svelte components have built-in scoped styles. Plain CSS inside a <style> tag is automatically scoped to that component, like CSS Modules with zero setup. You can use it alongside Tailwind for the cases utilities handle awkwardly.
What I picked: Tailwind CSS v4. Utility-first is simply fast for a solo project, there is no custom CSS file to maintain, and the Vite plugin makes setup a one-liner in SvelteKit. I keep Svelte’s <style> blocks in reserve for anything genuinely custom.
Layer 7: The polish layer
The top of the stack: how things move. Animation gets dismissed as decoration, but transitions are often what separates a site that feels professional from one that feels like a homework assignment.

The practical reality: most projects need enter/exit transitions, hover effects, and maybe a scroll-triggered reveal. That is it. CSS animations cover 80 percent of those cases in any framework. GSAP is the professional tool when you genuinely need timelines and scroll choreography, which is why marketing agencies live in it. Framer Motion is excellent but heavy; notice that it weighs as much as React itself. And the browser-native View Transitions API is quietly making page-level transitions free.
What I picked: Svelte’s built-in transitions. fly, slide, and fade are part of the framework, compiled in at zero added bundle weight, and they cover everything my timeline and assessment flow need. This was the layer where choosing Svelte at Layer 5 paid a second dividend.
The hidden rule: one choice, five consequences
If you zoom back out, a pattern emerges that no single-layer comparison will show you. The meta-framework is the keystone decision. Choose Layer 4 and you have implicitly chosen Layers 1, 3, and 5, plus the natural shape of Layer 2:
Pick this... ...and you get
─────────────────────────────────────────────────────
SvelteKit → Svelte + Vite + built-in endpoints
Next.js → React + Turbopack + API routes
Nuxt → Vue + Vite + server routes
Astro → Any UI + Vite + built-in endpoints
Fresh → Preact + esbuild + Deno handlers
Only styling and animation remain genuinely independent choices.
This is why “Vite vs Next.js” is a confused question: Vite is Layer 3 and Next.js is Layer 4, and choosing Next.js means choosing Turbopack, while choosing almost anything else means choosing Vite. The tools were never alternatives. They are answers to different questions, occasionally bundled together.
For quick reference, here is how I would map common project types to stacks:
Project type Stack I would reach for
──────────────────────────────────────────────────────────────
Content/marketing site SvelteKit or Astro + Tailwind
SaaS with complex UI Next.js or SvelteKit + Tailwind
Enterprise app, large team Next.js or Angular + CSS Modules
Performance-critical SPA SolidStart or SvelteKit
Blog/docs site Astro + Tailwind
API-first backend Fastify or Hono
Edge/serverless API Hono
Enterprise backend NestJS
Prototype / MVP SvelteKit or Next.js + Tailwind
Heavy scroll animation site Astro + GSAP
What I actually shipped
Here is the full stack for my project, layer by layer:
Layer 1 | Runtime: Node.js
Layer 2 | Server: SvelteKit built-in (no dedicated framework)
Layer 3 | Bundler: Vite 7
Layer 4 | Meta-framework: SvelteKit 2
Layer 5 | UI framework: Svelte 5
Layer 6 | Styling: Tailwind CSS 4
Layer 7 | Animation: Svelte built-in transitions
Deployment: Netlify (via adapter)
The result for a content-heavy site with interactive components: roughly 16KB of JavaScript shipped to the browser, versus 90KB or more for the equivalent React stack, before I write a single feature. No animation library, no CSS files to maintain, no backend to deploy. Two of the seven layers (server framework and animation library) turned out to be layers I did not need at all, and knowing the map is what made skipping them a confident decision instead of a guilty one.
That is the real payoff of thinking in layers. The next time a shiny new tool tops Hacker News, you will not ask “should I switch to this?” You will ask “which layer is this, and is it better than what that layer already gives me?” Most of the time the answer is no, and you can get back to building.
If you keep one thing from this article, keep the diagram. Seven layers, seven questions, one decision that drives the rest. The JavaScript ecosystem is not chaos. It just needed a map.
Conclusion
By mapping your tools to these seven layers, you can build with confidence, avoid the hype cycle, and design web applications built to last.
If you enjoyed this layer-by-layer breakdown of the modern web stack, follow me here on Medium and subscribe to get notified of my future pieces.
메타데이터
- post_id
- 030e8b28c095
- slug
- every-web-project-is-seven-decisions-a-layer-by-layer-guide-to-the-modern-web-stack-030e8b28c095
- url
- https://medium.com/@neosrix/every-web-project-is-seven-decisions-a-layer-by-layer-guide-to-the-modern-web-stack-030e8b28c095
- canonical_url
- https://medium.com/@neosrix/every-web-project-is-seven-decisions-a-layer-by-layer-guide-to-the-modern-web-stack-030e8b28c095
- author_url
- https://medium.com/@neosrix
- status
- ok
- fetched_at
- 2026-06-12 22:02:08