· 7 min read ·

The Authoring Tax: What Frontend Frameworks Were Actually Solving

Source: lobsters

The claim that you barely need a frontend framework with AI is worth taking seriously, but it requires separating two things that usually travel together: the cost of writing the code, and the architecture the code needs to be correct. AI can reduce the first. It does not change the second. Understanding where each framework’s value actually lives determines whether the argument holds for your situation.

Why Frameworks Appeared

The DOM in 2008 was a genuinely hostile environment for state management. You had getElementById and innerHTML. Synchronizing a list of items between a data model and the visual representation required manual bookkeeping, and the failure mode when that bookkeeping drifted was a displayed UI that no longer reflected the underlying state. jQuery helped with traversal and cross-browser inconsistencies, but it could not solve the state synchronization problem because that problem is structural, not syntactic.

React arrived in 2013 with a specific answer: make the UI a pure function of state. You declare what the component should look like given a value, and the library handles propagating changes. Vue reached the same destination through a reactive data binding model. Angular built a full application framework with dependency injection, routing, and templating in one package. These are different implementations of a similar insight: the hard part of frontend development is managing state changes over time, and frameworks should absorb that complexity.

But nearly all the complexity they absorbed was complexity at authoring time. React’s component model, its JSX syntax, its unidirectional data flow, its hooks system, its reconciliation algorithm: all of these exist because humans writing large frontend applications were consistently getting state management wrong in vanilla JS. The framework is a set of rails that prevent a category of mistakes.

The runtime benefit is real but narrower than often described. The virtual DOM diffing algorithm provided performance wins in 2013 when the alternative was innerHTML reassignment on every state change. That advantage has shrunk as browsers optimized their own rendering paths and as techniques like incremental DOM and signals-based reactivity proved that the virtual DOM was one approach among several.

Authoring Cost vs. Runtime Architecture

Consider a concrete case. Here is a simple counter in vanilla JS:

let count = 0;
const btn = document.getElementById('increment');
const display = document.getElementById('count');
btn.addEventListener('click', () => {
  count++;
  display.textContent = count;
});

And the same thing as a React component:

import { useState } from 'react';
export function Counter() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <span>{count}</span>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
    </div>
  );
}

For this case, React provides nothing architecturally. The vanilla version is shorter and has no dependencies. The React version has a cleaner separation of state and DOM updates, but the problem is simple enough that no separation is needed.

Now scale this to an application with a shopping cart, a user session, async product lookups, optimistic updates, and error states that interact with each other. The vanilla version stops being simple. You need to decide where state lives, how components communicate, how to batch updates without visual inconsistency. React’s model does not solve these problems by magic; it provides a disciplined vocabulary for expressing them. That vocabulary is what teams converge on.

The key point: the vocabulary problem is an authoring problem. Multiple developers on the same codebase need shared conventions for how state flows and how components compose. React’s component model is largely a solution to coordination, not to computation.

What AI Actually Changes

AI coding assistants dramatically reduce the cost of writing the boilerplate. The ceremony around React, the useEffect dependencies, the prop drilling avoidance, the context setup, the TypeScript generics on custom hooks: an experienced developer can describe the shape of a component and get a correct first draft in seconds. The parts that were tedious are now cheap.

What AI does not change is whether your application’s state management is coherent. If you have three components each maintaining their own copy of user preferences, and two of them fall out of sync on a network error, that is an architectural problem. It exists in vanilla JS and it exists in React. The framework nudges you toward patterns that prevent it; the nudge is cheaper to receive than to design yourself.

Andrej Karpathy’s “vibe coding” framing captures how AI-assisted prototyping actually feels: you describe intent, accept generated code, iterate. This works well when the intent is narrow and self-contained. It accumulates technical debt quickly when the generated code has implicit state management assumptions that conflict across components, because those conflicts are not visible until the application grows large enough for them to collide.

The specific work dlants.me’s VAMP approach demonstrates is that for single-author, well-scoped tools, AI can generate coherent vanilla JS fast enough that the framework setup cost becomes unjustifiable. That conclusion is probably correct within those constraints.

HTMX took a different approach by returning state management to the server and using hypermedia exchanges for UI updates. For applications where the server is the source of truth and user interactions map cleanly to server actions, this eliminates the client-side state problem by not having one. It is not a solution to frontend complexity; it is a redefinition of what the frontend is responsible for.

Alpine.js occupies a narrower niche: progressive enhancement on server-rendered HTML, where you need small islands of interactivity without a build step. For sprinkling behavior onto multi-page applications, it is often the right size.

Web Components and Lit are worth distinguishing from both. Web Components provide browser-native encapsulation with shadow DOM and custom element registration. Lit is a lightweight layer over the Web Components API that makes authoring less verbose. Together they give you reusable components with no framework dependency. The browser support story is now good enough that the main cost is the relative immaturity of the tooling ecosystem compared to React.

All of these movements share a premise: React-sized frameworks are too heavy for problems that are not React-sized. That premise was correct long before AI entered the conversation.

Where the Argument Holds

The “no framework needed” position is defensible for: single-author projects where coordination overhead does not exist; prototypes with a short expected lifespan; tools with limited state surface area; applications where server-rendering covers most of the UI and interactivity is isolated to a few components.

In these cases, the framework’s authoring benefits are reduced because the codebase is small enough to hold in your head, and the coordination benefits are zero because there is only one developer. AI reduces the remaining authoring cost further. The ecosystem and tooling still carry some overhead (build configuration, bundle size, learning curve for new contributors), and for a small project that overhead is real.

Where It Breaks Down

The argument gets weaker as team size increases, as state complexity grows, and as the application lifetime extends. Consider what a framework like React actually ships with in a production context: React Router or the routing layer built into Next.js, React Testing Library for component testing, browser devtools extensions for inspecting component trees and profiling renders, a mature ecosystem of accessible component libraries, state management tools like Zustand or Jotai that handle cross-cutting state concerns.

These are not incidental. They represent years of accumulated solutions to problems that frontend teams encounter reliably. Reaching for vanilla JS or a minimal framework means either rebuilding these solutions or accepting that you do not have them. AI can generate a client-side router; it cannot replace the community knowledge embedded in how React Router handles edge cases in browser history management.

There is also a maintenance dimension. Code you did not write but accepted from an AI still needs to be understood during debugging. A vanilla JS state management approach that the original author (or AI) considered self-evident may be opaque to the next developer, or to yourself six months later. Frameworks impose a vocabulary that gives future maintainers a starting point for reasoning. Vanilla code imposes no vocabulary, which is a liability when the implicit vocabulary in the generated code is only coherent to the original author.

The Honest 2026 Calculus

The right question is not “do I need a framework.” It is “what is the coordination surface of this project, and what is its expected maintenance lifetime.”

For a small tool built by one person and potentially maintained by that person: AI-generated vanilla JS or a minimal library like Preact or Alpine.js is probably the right choice. The framework tax exceeds the benefit.

For a team application expected to grow: the framework’s coordination and ecosystem value compounds over time. The authoring cost that AI reduces was real but never the dominant cost in team development. Review, onboarding, debugging, and refactoring dominate, and frameworks make all of those easier by providing shared vocabulary.

For anything with serious routing, code splitting, server-side rendering, and accessibility requirements: the argument for building that from scratch is not stronger because AI exists. It is slightly less painful, but the result is a framework you built yourself rather than one with years of production hardening behind it.

AI genuinely changes the authoring calculus. It does not change what well-structured frontend code needs to be.

Was this interesting?