The industry ran a forty-four-meter circle and landed in your living room
David Poblador's What Happened to the Frontend is the best map of the last two decades I have seen. Read it if you haven't. The thesis at bedrock is worth quoting:
"After two decades and forty-four meters of build tools and bundlers and hydration schemes, the industry sprinted in a giant circle and arrived, slightly out of breath, back at something that looks an awful lot like the file you uploaded over FTP."
He is right. The frontier of 2026 is the thing we had in 2008: server-rendered HTML, minimal JavaScript, the platform doing the heavy lifting. But David stops at describing the pendulum swing. I want to name the mechanism that made it inevitable, because once you see it, the next step is obvious.
Frameworks were never the destination. They were the W3C's R&D department - a distributed, global, unpaid prototyping lab that explored every idea the platform needed, crashed on the ones that didn't work, and handed the survivors to the standards bodies. Now the standards bodies have finished their homework. And a second force - large language models that write JavaScript as fluently as they write English - just made the framework layer structurally unnecessary for a huge and growing class of work.
This is not "frameworks bad." This is "frameworks completed their mission. Let them rest."
How the playground worked: every framework was a W3C prototype
The pattern is so consistent it is almost boring once you see it. Someone hits a wall with the platform. They build a library to work around it. The library becomes popular. The W3C (or TC39, or WHATWG) watches, learns, standardises. The library's reason for existing evaporates.
jQuery → querySelector, fetch, classList, Array.from
jQuery solved real pain: the DOM API was verbose, inconsistent across browsers, and missing common
operations. Every $("#cart").toggleClass("active") was a prayer that you weren't on IE6.
What the W3C absorbed: document.querySelector and querySelectorAll (Selectors
API, 2013), element.classList (2010, 2014 across browsers), fetch (2015,
replacing XMLHttpRequest), Array.from, Object.assign,
Array.prototype.includes. What remains of jQuery's reason to exist? Essentially nothing.
Nobody starts a jQuery project today, and the sites still running it do so because they stopped evolving
in 2014.
React → the platform learned components, reactivity, and declarative UI
React's big idea - describe the UI as a function of state, let the runtime reconcile the difference - was genuinely new in 2013. The Virtual DOM was the implementation strategy, not the insight. The insight was declarative UI.
What the W3C and TC39 absorbed:
-
Web Components (Custom Elements v1, 2016; Shadow DOM, 2018) gave the platform a native
component model.
customElements.define("my-button", MyButton)- no framework required. - Signals (TC39 proposal, Stage 2 as of 2025, shipping in Solid.js, Preact Signals, Angular's signals, and now progressing through standards) are the final abstraction of React's state → UI pipeline. A signal is a value that knows who depends on it. You change the value. The dependent UI updates. That is the entire reactive loop, expressed as a language primitive.
- Declarative Shadow DOM (2023) lets you render Shadow DOM on the server without JavaScript.
-
<template>and<slot>gave us native content projection without JSX.
What remains of React's unique territory? Less every year. The JSX syntax will survive as a convenience, but the runtime - the reconciler, the fiber tree, the synthetic event system - is increasingly redundant against native APIs.
Webpack / Babel → ES Modules, Import Maps, and the native build
The build step was never fun. It was a workaround for a platform that didn't have modules, didn't understand modern syntax, and punished you with HTTP/1.1 round-trips for every file.
What happened:
-
ES Modules (
import/export, 2018 in all major browsers) ended the module fragmentation war. CommonJS, AMD, UMD - all fossils. -
Import Maps (2021) let you control module specifiers in the browser without a bundler:
{"imports": {"lit-html": "https://cdn.jsdelivr.net/npm/lit-html@3/"}}. No webpack. No config. - Native module loading in browsers (2018+) is fast enough that HTTP/2 multiplexing makes bundling less necessary for development and often for production.
- Modern JavaScript syntax (optional chaining, nullish coalescing, private fields, top-level await) is supported in every browser that matters. No Babel pass needed.
-
type="module"scripts load deferred by default, scoped by module, cleanly separated. No IIFE wrapping, no global leaks.
CSS-in-JS / Sass → native CSS
This one is almost embarrassing in retrospect. We reached for Sass, PostCSS, Styled Components, CSS Modules - all to compensate for features that CSS has now natively absorbed:
- Nesting (2023+) - the top reason people used Sass.
- Container Queries (2022+) - responsive components without media queries against the viewport.
-
:has()(2022+) - a parent selector, which Sass never even attempted. "Style the parent if it contains a child matching X" - the web's most-requested feature, delivered by the platform, impossible in any preprocessor. -
Cascade Layers (
@layer, 2022+) - specificity management without!importantscreaming or BEM naming gymnastics. -
Custom Properties (2016+) - dynamic theming without a preprocessor variable.
color-mix(),light-dark(), andrelative color syntax(2023–2025) closed the remaining gaps.
State management (Redux, MobX, Zustand) → Signals + structured clone
Every JS framework built a state management story because the platform didn't have reactive primitives.
Now TC39 is standardising Signals, structuredClone handles deep copies, and
BroadcastChannel + SharedWorker covers cross-tab state sharing. The remaining
use cases are genuinely application-specific, not infrastructure missing from the platform.
The artificial complexity tax (and who's been collecting it)
Guseyn, who built a complete SaaS product - instruxmusic.com - in vanilla JavaScript with Web Components, puts his finger on something I have felt for years but never articulated this cleanly:
"I tend to believe if something requires from you more lines of code and more abstractions in an abstract framework to achieve than in native technology, then maybe this framework does not really make your life easier."
He crystallises the framework mindset into five commandments, and reading them as a list makes the pattern embarrassing:
- NO, YOU CANNOT MANIPULATE DOM DIRECTLY, ONLY WE CAN DO THAT INSIDE OF OUR FRAMEWORK.
- NO, YOU CANNOT JUST ATTACH EVENT LISTENER TO WINDOW OBJECT. YOU NEED TO WRAP IT.
- NO, YOU CANNOT JUST USE GLOBAL STATE. USE THIRD-PARTY LIBRARY WITH FANCY FUNCTIONAL DESIGN.
- NO, YOU CANNOT USE NATIVE WEB COMPONENTS. YOU MUST USE HOOKS AND OUR LIFECYCLE WE INVENTED.
- NO, YOU CANNOT USE IMPORT MAP. YOU MUST USE A BUILD TOOL AND PREPROCESSORS.
Each of these is a ban, not a simplification. They are walls built around a captive audience. The native web platform never banned any of those things - frameworks did, because controlling the escape hatches is how frameworks retain relevance.
And yet the industry swallowed it. We accepted that writing HTML meant picking from a shelf of twenty-odd templating languages (Jinja, Pug, Handlebars, Nunjucks, Mustache, EJS, Blade, Twig, ERB, Haml, Slim, Thymeleaf, Tera, Askama, Maud - and that is not exhaustive). We accepted that CSS needed a preprocessor (Sass, Less, Stylus, PostCSS, Myth - all solving problems the platform has since absorbed). We accepted that JavaScript was somehow not good enough and needed to be written in TypeScript, CoffeeScript, PureScript, ReasonML, Elm, ReScript, ClojureScript, or Dart. The tower of Babel was not a bug. It was a business model.
React, in particular, is a masterwork of branding. The name evokes speed, dynamism, life - the opposite of what it actually does, which is interpose a ~45 KB reconciler between you and the platform. As Guseyn notes:
"It's a strong name, isn't it? It gives speed and power. It gives something dynamic, positive and full of life."
The irony is that the fastest thing you can do is nothing. No framework runtime. No reconciliation. No hydration. The browser already has a rendering engine. It is extremely good at its job. You do not need to simulate it in JavaScript.
The pattern is finished
Look at the list above. Every major innovation of the framework era - components, reactivity, modules, bundling, styling, state - has either shipped as a native web API or is actively progressing through standards. The ones that are still framework-only are either too niche to standardise (JSX's ergonomics, which you can replicate with tagged template literals) or too new to have reached consensus yet.
The frameworks served a function analogous to the DARPA of the web platform: they took risks, iterated fast, burned down bad ideas, and handed the survivors to the standards bodies. That function is not infinite. At some point the prototyping phase ends and the standardisation phase produces a platform that makes the prototypes unnecessary.
That point is now.
Enter the second force: the LLM writes the glue
If the platform had matured in isolation, we would still need something to wire components together, handle routing, manage data fetching - the application-level orchestration that no standards body will ever define because it is inherently non-standard. A decade ago that "something" was a framework. Today it is an LLM.
Here is the crucial insight that most framework advocates miss:
An LLM generates JavaScript, not React. It generates HTML, not JSX. It generates CSS, not Sass. The more you lean on the platform, the better the LLM's output is - because the LLM was trained on the entire platform, not on any single framework's subset.
When I prompt an LLM with "build me a product page with a cart button that updates a badge count," it returns:
<script type="module">
import { signal, effect } from "./signals.js";
const count = signal(0);
document.querySelector("#add-to-cart")
.addEventListener("click", () => count.set(count.get() + 1));
effect(() => {
document.querySelector(".cart-badge").textContent = count.get();
});
</script>
That's ~200 bytes of vanilla JS. It uses platform APIs (querySelector,
addEventListener, textContent, type="module"). It uses a
lightweight signal library (which exists on a CDN, zero install). It has no build step, no JSX transform,
no Virtual DOM, no hydration, no suspense boundaries, no server components, no "use client" directives. It
works in every modern browser. It will work in every future browser.
It was written by an AI in three seconds.
Now contrast what the same LLM generates when it's been trained on React tutorials:
"use client";
import { useState, useEffect } from "react";
import { useCart } from "@/hooks/use-cart";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
export function AddToCart({ productId }) {
const [count, setCount] = useState(0);
const { addItem } = useCart();
useEffect(() => {
// 27 lines of hydration safety, SSR guards,
// and a dependency array that will cause a lint warning
}, [count, productId, addItem]);
return (
<div className="flex items-center gap-2">
<Button onClick={() => { setCount(c => c + 1); addItem(productId); }}>
Add to Cart
</Button>
<Badge>{count}</Badge>
</div>
);
}
That's ~600 bytes of source code that ships ~30 KB of framework runtime (React + ReactDOM + the component
library). It requires a build step, a bundler config, a tsconfig.json, a
tailwind.config.js, a components.json, 250,000 files in
node_modules, and a deployment platform that understands the meta-framework of the week. The
AI wrote it in three seconds too. But the AI is reflecting the framework's assumptions, not the
platform's capabilities.
The difference is not about developer skill. It is about which set of defaults you carry into the prompt.
"The browser is already the framework"
This is the single line from Guseyn's essay that cuts the deepest:
"The browser engine is a huge and complex piece of software that handles so much for you that the only thing you really need is just to write dynamic files with HTML, CSS and JavaScript and a browser engine glues everything for you."
He built instruxmusic.com - a full SaaS for music schools with lesson scheduling, automated invoicing, video chat with file sharing, a brand page builder with custom domains, a booking system, dashboards, stats, messaging - using exactly this philosophy. Web Components. Import maps. Zero build step. Zero framework runtime. The whole application, backend and frontend, is vanilla JavaScript, HTML, and CSS.
And here is the number that should make every framework advocate pause:
Frontend only:
-----------------------------------------------------------------
Language files code
-----------------------------------------------------------------
HTML 54 17687 ← 50%+
CSS 6 5437 ← 15%
JavaScript 102 9252 ← 35%
-----------------------------------------------------------------
SUM: 162 32376
-----------------------------------------------------------------
Nine thousand lines of JavaScript - frameworks included - for a complete SaaS product, running in production, serving real customers.
HTML is over half the codebase. CSS is 15%. The JavaScript is a minority of the application. This is the exact inverse of the typical React app, where the JavaScript bundle dwarfs everything else because it carries the framework runtime, the component library, the state management library, the routing library, the data fetching library, and the thousand other things you "need" to build a modern web app.
Guseyn's approach is not an exception. It is a template. Custom elements, import maps,
<template> for declarative rendering, native form handling with JSON, HTML caching for
multi-page navigation that feels like SPA. Every technique uses the platform as designed, not as a target
to be transpiled to.
And the best part? He writes:
"You don't need AI to build with this approach. Or at very least you definitely don't have to spend so many tokens."
Because when your codebase is 50% HTML and 15% CSS, the surface area the LLM needs to cover is small. The LLM writes the JavaScript glue - the event listeners, the API calls, the custom element lifecycle. The HTML and CSS are already declarative. They are the spec.
The whole stack collapses to this
A 2026 frontend built for vanilla JS and LLM collaboration:
Project root:
├── index.html ← server-rendered or static, no build
├── styles.css ← native CSS, nesting, layers, container queries
├── scripts/
│ ├── main.js ← type="module", imports from CDN
│ ├── components/
│ │ ├── cart.js ← custom elements + signals
│ │ ├── product.js
│ │ └── search.js
│ └── utils/
│ ├── signals.js ← ~50 lines, or import from CDN
│ └── api.js ← fetch, no client state library
├── assets/
└── _headers ← cache policy, CSP
Zero package.json. Zero node_modules. Zero build steps. Zero
tsconfig.json. Zero framework-specific CI config. Zero lockfiles. Zero migrations every two
years. Zero of the 250,000 files that a blank create-next-app deposits on your filesystem.
Deployment: copy to a CDN. Or GitHub Pages. Or Netlify with --no-build. Or a single Nginx
folder.
Performance: whatever the platform gives you, which is a lot - native ESM is deferred, CSS
content-visibility: auto handles virtualisation, loading="lazy" on images,
fetchpriority="high" on the hero, dns-prefetch and preconnect for
third parties. No framework runtime at all. Every byte is yours.
You cannot optimise faster than zero.
The objections (answered quickly)
"You need TypeScript for any serious project."
TypeScript's value is real. But // @ts-check plus JSDoc annotations gives you type checking
on vanilla JS without a build step. Or use tsc --noEmit as a lint pass - it checks
types and emits nothing. The LLM will generate the JSDoc annotations. You get autocomplete, you get type
errors at save time, you get zero compilation overhead. Is it as ergonomic as TSX with full type
inference? No. Does it cover 90% of the value? Yes.
"Web Components are ugly/hard/not ready."
This was true in 2018. Custom Elements v1 is shipped in every browser. Declarative Shadow DOM is shipped.
The HTML template element, slots, part and exportparts for styling - all
shipped. The developer experience is not JSX-level ergonomic, but it is platform-level stable,
which means any code you write today will work in 2035. The LLM handles the boilerplate. Lit and Shoelace
prove that the ergonomics can be excellent.
"You still need a framework for a large app."
Define "large." The largest frontend apps in the world - Google Docs, Figma, VS Code - are not built with React. They are built with platform APIs, custom rendering pipelines, and carefully owned abstractions, because the framework would get in the way at that scale. If your app is smaller than Figma, you almost certainly don't need a framework. If your app is Figma, a framework is the last thing you want.
"You lose the ecosystem."
The ecosystem of 2026 is mostly a liability. Every shadcn/ui component is copy-pasted into your repo anyway. Every Tailwind utility is a CSS rule you could write yourself. Every React hook is a function you could write in vanilla JS. The ecosystem is a crutch that became a cage. The LLM is your ecosystem now - it has memorised every good pattern from every library, and it generates them on demand without the import.
"Vanilla JavaScript does not have a structure. It does not scale."
This is the most persistent objection, and Guseyn dismantles it with a single number:
9,252 lines of JavaScript - frameworks included - for a complete SaaS product with billing, video chat, booking, dashboards, and a brand page builder.
Let that sink in. A production SaaS, running today, serving real music schools, with less JS than a single
node_modules dependency tree walk. And the codebase is 50%+ HTML, because the structure is
not in the JavaScript - the structure is in the HTML. Custom elements give you a component model.
Import maps give you module resolution. The browser gives you the runtime. The framework you were told you
"need" for structure was actually the thing generating most of the code you are maintaining.
"Sure, it is messy. You know why? Because for the last 20 years, instead of solving real issues and making good decisions on how to simplify code, we just invented so many abstractions, languages and tools just to avoid the problem instead of solving it."
The structure argument is a self-fulfilling prophecy. Frameworks are necessary because frameworks are the only thing people learn. The platform is dismissed as unstructured because nobody teaches platform architecture anymore. But Guseyn's numbers prove it: the platform scales. It scales to a six-figure SaaS without a framework.
What frameworks gave us (say thank you, then let go)
This is not a hit piece on the people who built the frameworks. React literally changed how we think about UI. Vue's progressive design made frontend development accessible to millions. Svelte showed that compilation was a viable strategy. Astro proved that shipping less JavaScript was not a regression but an advance. Every framework team pushed the platform forward by building things the platform didn't have yet.
The debt is paid. The platform now has the APIs that were missing. The LLM now writes the orchestration that the framework used to supply. The node_modules directory - that two-hundred-megabyte tax on starting a project - is structurally optional for the first time since 2012.
You can keep using React. You can keep building Next.js apps. Nothing will stop you. The tools will continue to exist, maintained by talented people and an ecosystem that has learned to profit from complexity. But the justification for starting a new project on a framework is evaporating. The defaults have shifted. The platform is mature. The AI speaks platform JavaScript, not React JavaScript, unless you teach it otherwise.
The frameworks did their job. They ran the experiments. They crashed the prototypes. They handed the survivors to the W3C. The standards bodies standardised. The browsers shipped. The LLMs memorised.
The era of the framework was a necessary, productive, temporary phase of web platform evolution. It is over.
What this looks like in practice
Here is a concrete workflow that works today, on this blog, without a framework:
- Write markdown in
data/posts/. -
A tiny SSG (mine is under 500 lines) runs at build time: parses frontmatter, renders HTML through a
template engine, writes flat files to
src/posts/. -
The templates are HTML with a small
{% block %}syntax - no JSX, no components, just template inheritance. -
Interactivity is added via
<script type="module">in the HTML file, importing from CDN sources where needed. - CSS is native - nesting, layers, custom properties, container queries. No preprocessor, no PostCSS plugins I don't control.
- The LLM (Claude, Cursor, Copilot) generates the boilerplate: the SSG pipeline, the template tags, the interactive enhancements, the meta tags, the structured data, the accessibility attributes. I review, I commit, I deploy.
The result is a site that loads faster than 99% of the framework-built web, contains zero unused JavaScript, requires zero dependency updates, and will render identically on the browser of 2035 because it uses nothing but platform APIs.
This is not nostalgia. This is the frontier.
The closing argument
David Poblador ends his descent with this:
"The frontier of 2026 looks an awful lot like the file you uploaded over FTP."
I agree. And I add: the reason you can go back to that file - with better CSS, better JavaScript, better performance, and an AI that writes the boring parts - is that the frameworks finished the work they set out to do. They explored the design space, the platform absorbed the lessons, and the platform is now good enough.
The frameworks were a scaffold around an unfinished building. The building is finished. Take down the scaffold.
Vanilla JS plus an LLM is not a regression. It is the final form of an evolution that began when someone
typed $("#cart").load("/items") and thought "there has to be a better way." There is now. The
better way is the thing you started with, grown up, with an AI at your side.
Ship HTML. Enhance with vanilla JS. Let the LLM write the glue. The platform handles the rest.
Welcome back.