If you spend long enough looking at the web development landscape — frameworks, libraries, runtimes, build tools, the whole sprawling field — you eventually notice that the surface complexity hides a much smaller number of underlying patterns. Most of the noise is marketing. Underneath, the choices are simpler than the industry pretends.
This piece is about those underlying patterns, about the difference between learning the function of a tool and learning its marketing label, and about something quieter that sits beneath the whole conversation: the question of when a person is allowed to start.
Two patterns
Strip away the brand names, and most web development falls into one of two patterns.
The first is template-first. You declare the HTML or UI structure as the primary artifact, and logic attaches to that structure to manipulate it. JSX, Razor, Blazor components, Vue single-file components, Angular templates, Svelte, the old ASPX markup — all of these treat the template as the source of truth. Logic is bolted on to make the template do things.
Because the template is primary and logic is secondary, you need a separate state engine. The template doesn’t naturally hold state, so something else has to coordinate what it shows. Redux, Vuex, Pinia, MobX, Zustand, signals, observables, ViewState, two-way binding — these are not accidents. They exist because template-first authoring structurally separates “what the UI looks like” from “what the program knows,” and the gap between those two has to be bridged by machinery.
The second is logic-first. Code is the primary artifact. HTML is produced by the code as strings – StringBuilder.Append, echo, print, interpolation. There is no separate state engine, because state lives where logic lives: in normal variables, in normal scope, in normal control flow. The HTML is an output, not a structure to be manipulated. When the request finishes, the state is gone, because the request is the lifecycle.
This is the older pattern. Classic PHP. Old-school ASP. Server-rendered Rails. Django templates in their simplest form. And, for those still working in the .NET world, the Vanilla and Pageless Web Forms approach – string-based rendering inside a RequestHandler, no ViewState, no postbacks, no template lifecycle pretending to be stateful when the underlying transport is not.
Why the distinction matters
The template-first pattern made one choice, and the consequences of that choice ripple through everything: lifecycles, hydration, reconciliation, virtual DOMs, hooks, effects, mount and unmount and cleanup. All of this complexity is the cost of keeping a persistent template tree coherent with changing state. It is real, useful work — but it is work that exists because of the pattern, not because the web required it.
The logic-first pattern doesn’t have any of that machinery, because it doesn’t have the problem the machinery solves. Variables hold state. Functions return strings. The browser receives HTML. The cycle ends. When something changes, the next request produces the next page. Simple, and old, and direct.
Neither pattern is wrong. They solve different shapes of problem. But it is worth seeing clearly that they are different patterns, and that one of them brings a permanent tax that the other does not.
A third pattern: the browser as runtime
There is a third pattern, and it deserves to be named separately because the industry tends to confuse it with the first one.
When you stop treating the browser as a document viewer and start treating it as a deployment target — a sandboxed virtual machine with graphics, networking, storage, and a JIT compiler — then shipping a 500MB JavaScript application is not absurd. It is just application deployment. Figma. Photoshop on the web. Google Earth. VSCode in a browser tab. Excel Online. Blender’s web port. Linux distributions running through WebAssembly. These are not websites. They are applications that happen to use the browser as their runtime, because the browser is the most universally installed runtime on Earth.
The cultural assumption that “web means small download” is a leftover from when web meant document. Modern Electron apps are 200 to 500 megabytes before you write a line of code. Slack, Discord, Teams, native VSCode — all bundle Chromium. iOS apps routinely exceed 500MB. AAA games are 100GB. Comparing a Figma bundle to a 1995 marketing page is comparing two different things that happen to share a transport protocol.
So the third pattern is the browser as application runtime, and it is legitimate. The first two patterns were both answering the question, what do we send back when a user requests a URL? This third pattern answers a different question entirely: we want to ship an application — what’s our deployment vehicle? The browser is the chosen VM. The fact that it loads via HTTP is incidental, not essential.
Where the actual error lies
The error in the modern industry is not that any of these three patterns exist. Each was built to solve a real problem, by real people, in a real context.
The error is that the third pattern — application-runtime — got applied to the work of the first two. Marketing sites built in Next.js with hydration. Blogs that ship two megabytes of JavaScript to display 800 words of text. Forums that take four seconds to become interactive so they can do what <form action="post.php"> did instantly in 1998. Admin panels that need a 200MB node_modules folder to render a table.
If you are building a vector editor, a 3D modeler, a spreadsheet engine, a video editor, an IDE — the browser is your VM, and 500MB is appropriate. You are writing software, not authoring a document. The lifecycle complexity, the state engine, the virtual DOM, the compiler pipeline — these are the cost of building software, and the cost is worth paying when you are actually building software.
If you are building a document — an article, a forum thread, a product page, a CRUD admin, a dashboard — the browser is already the runtime you need. HTML is the format. String interpolation delivers it. JavaScript handles whatever interactions need handling. Shipping an application runtime to render a forum post is not modern; it is malpractice, and the malpractice is structural, not aesthetic.
Function, not marketing
The way out of this is small in description and large in consequence: learn the function of tools, not their marketing.
The industry teaches tools as identities. I’m a React developer. We’re a Next.js shop. Web Forms is dead. These are tribal banners. They tell you which camp a person belongs to. They tell you nothing about what the tool does mechanically, what problem it was shaped to solve, or what trade-offs it made along the way.
Every tool exists with its own story. React solved a problem Facebook had with synchronizing UI state across a massive interactive application. Web Forms solved the problem of giving Windows desktop developers a way to build web apps without learning HTTP. PHP solved Rasmus Lerdorf’s problem of tracking visitors to his resume page. Each one has a purpose, a shape, a fit. None of them is universally correct, and none of them is universally wrong.
When you know the function — what the tool actually does, what it was built for, what it costs — then you can match it to the task in front of you. A blog needs to render text and accept comments; string interpolation does that perfectly. A vector editor needs to manage thousands of interacting objects in real time; that is what a frontend runtime is for. A line-of-business CRUD application needs forms, tables, and reports; ASP.NET Web Forms was literally designed for that. Different shapes, different fits.
The marketing layer obscures all of this. It tells you React is modern and Web Forms is legacy, as if modernity were a function. It tells you to choose a stack before you have understood the problem. It treats tools as products to be loyal to instead of instruments to be wielded. The cost is everywhere — bloated blogs, frameworks misapplied to documents, careers spent learning vocabulary without ever touching mechanics.
The only metric that matters
Underneath all of this is one question, and it is the only honest measurement in the entire field:
Did the problem get solved?
Did the client get what they needed. Did the thing ship. Does it work tomorrow when someone needs it. That is the surface the world actually touches. The dependency graph, the state architecture, the framework choice — these are the developer’s private world, and the developer’s private world is not what was purchased.
The client does not see your stack. They do not care whether you shipped on a Titanic, a yacht, or a fishing boat. They care that the thing arrived. A working application built in unfashionable tools beats an unfinished application built in fashionable ones, every time, in every context, without exception.
Once you commit to that metric, a new kind of alignment forms. Not alignment with a framework’s roadmap. Not alignment with the conference circuit. Not alignment with what is being praised on social media this quarter. Alignment with the problem itself. Alignment with the tool that, in your hands, with your knowledge, on this client’s infrastructure, with this timeline and budget, gets the thing across the finish line.
That alignment is personal, and it has to be. Two developers facing the same problem can legitimately reach for different tools, and both can be right, because the tool is not being matched to the problem in the abstract — it is being matched to the problem plus the developer who has to wield it. Your fluency, your debugging instinct, your ability to reason about the system at two in the morning when something is broken, the libraries you have already written and trust — all of these are part of the equation. A theoretically optimal tool that is unfamiliar in your hands is worse than an “outdated” tool that sits in your hands like an extension of your thought.
The framework wars become irrelevant background noise once this stance is taken. You are not in the war. You are at a workbench, with a problem in front of you, and a tool that fits, and a deadline. That is the whole job.
The layer underneath
There is one more layer, and it sits underneath everything else, and it is the layer most people never quite reach.
Imagine asking a person to draw a painting or make a craft. A child of five draws something. A child of twelve draws something. A teenager draws something. A university art student draws something. A master with forty years of practice draws something. Each is shaped by their age, their experience, their intuition, their imagination, and the constraints of their circumstances.
Which is legitimate? Which is acceptable?
All of them.
A child’s drawing is not a failed adult drawing. It is a complete child’s drawing. It has its own integrity, its own logic, its own honesty. It expresses what that child saw and felt with the capacities that child had on that day. It is not a draft of something better. It is the thing itself.
The same is true of code. The first PHP script someone writes — the one that just echoes “hello world” and reads a name from a form — is a complete program. It solves a problem. It works. It is not a lesser version of what a senior engineer would write. It is what that person could build on the day they built it, and on that day, it shipped. That counts. That has always counted.
The industry has built an enormous mythology around readiness. Bootcamps, certifications, the senior-mid-junior ladder, you’re not ready for production until you understand X, gatekeeping wrapped in concern. The effect is that people who could already be building things instead spend years feeling unqualified to build the things they could already build. The kindergarten painter is told their painting does not count yet. So they put down the brush. And the world loses whatever they would have made.
But the truth is simpler and older than all of that. The work is made by whoever picks up the tools and makes it. Skill grows through making, not before it. Understanding deepens through shipping, not before it. The PhD developer and the self-taught teenager are both just people who touched the color. One touched it earlier and more often. That is the only real difference.
A kindergartner’s painting and a master’s painting both hang on walls. Both get looked at. Both move someone. The kindergartner’s painting hangs on a refrigerator and is the most precious object in that house. The master’s hangs in a museum and strangers weep at it. Neither is more legitimate. They are doing different work, for different viewers, in different rooms, and both are real. How should a kid paint? Should? You don’t. Just draw. There is no formula for how the work should sound, or look, or feel. There is no waiting until you are ready. There is no finishing course that finally qualifies you. Touch the color. You are already the painter.
Photo by Zaur Giyasov on Unsplash
