Formgong is a form backend with a dashboard, review widgets and a help chat. We write it with AI coding agents, and since late September it has grown to 251,000 lines of TypeScript. There's no React, Vue or Svelte in it, and not a single .tsx file. The pages are plain functions that return HTML strings.
Below is how that works at this size, with the numbers from the repo, and where we think it's starting to cost us.
First, one distinction, because the argument usually goes wrong right here. A library is code you call. A framework is code that calls you: it owns the lifecycle and you fill in the slots. React says it's a library, but in practice it owns rendering and state, so most people use it as a framework. By that test we do run one framework: Hono, on the server. We register handlers, and Hono decides when to call them. Everything on the page side is libraries, and most of those we wrote ourselves.
What replaced the framework features
Data binding. There isn't any, because the browser holds no state worth binding. Every change is an HTML form that posts to the server. The server writes to the database and answers with a 303 redirect to the same page, which renders again from the database. There are 260 such forms in our pages and 180 of those redirects. The page can't drift out of sync with the database because the browser never keeps its own copy of the data.
Components. These are functions that return strings. select(), field(), settingRow(), table(), dialog() and eight more live in one 212-line file. They arrived a month in, when the dashboard had grown several slightly different dropdowns.
Escaping. JSX escapes for you. We call esc() by hand, 3,808 times so far. This is the part a framework does best, and the part where a single slip turns into an XSS bug. An agent doesn't get bored on the 3,000th call the way a person does, but it can still forget one. So escaping has its own tests: 44 test files check it somewhere.
Interactivity. One 42 KB script hooks onto data-* attributes: copy buttons, code tabs, the monthly/yearly price switch. If it doesn't load, the links and forms underneath still work.
Conventions. Frameworks give a team one way to do things. We got that from a file the agents read before every task (AGENTS.md), and from tests that fail when someone drifts. One of them counts raw <button>, <select> and <table> tags in the views and fails the build if there are more than 366, 40 and 38. Any new code has to use the components. We built a linter for a framework we don't have.
Where AI changes the math
The usual case for a framework is that it saves people from boilerplate and keeps a team consistent. An agent doesn't mind boilerplate. It will write the same <label for> pattern hundreds of times without complaint, and it reads the conventions file before every task, which people mostly don't.
What an agent is bad at is noticing that those hundreds of copies have drifted apart. A framework stops that drift by making only one way possible. Without a framework you have to catch it yourself, and we do that with ratchet tests and code review. So we haven't skipped the framework's job. We do the same job with other tools.
Where we'd probably reach for one
The server-rendered pattern fits lists, settings and forms that save, which is most of a dashboard: 260 forms against 34 files of client-side code. It fits badly once a screen has real client state. Those 34 files call fetch and update the page by hand. Many are widgets that run on our customers' sites, where shipping a framework would be the wrong call anyway. The rest are the screens that really are apps: the form builder, the review import wizard, the drag-and-drop email editor. The editor is the extreme case. We ported an existing builder to plain TypeScript, and it's the one file in the repo where we'd consider a small reactive library if we started it today.
On the server we'd keep Hono either way. Routing, middleware, request parsing: that's exactly what a framework is for, it's one dependency, and nobody, human or agent, should rewrite it.
So, do you need one?
Here's where we've landed for our kind of product. When the server owns the state and the page is mostly forms, AI makes a frontend framework optional: the boilerplate it saves is cheap to write now, and conventions can live in a text file and a few tests. When the page owns the state, the framework is still doing real work, and an agent doesn't change that.
We'd like this to get argued in the comments. If you've let an agent build something without a frontend framework, where did it break first? If you think we've just reinvented React badly in 212 lines, tell us what we'll hit next.


