On not using a framework
A small, defensive essay about the things you learn by writing software without the thing everyone else is using, and why those lessons are not transferable to everyone but are real.
Last winter I built a small tool for a client — a configurator with a few inputs, a few outputs, and a server-side render — and I shipped it without a frontend framework. No React, no Vue, no Svelte, no HTMX. About 200 lines of vanilla JavaScript, plus a templating language that predates the term templating language. The client was happy. I was happy. I told a friend about it, and she asked, with the polite weariness of someone who has had this conversation before, “but why.”
Here is the version of the answer I gave her, slightly edited for honesty.
The honest version
The honest version is: the framework was the wrong tool for the size of the problem, and I knew it, and I would have had to talk myself into using it anyway, and talking yourself into a framework is a kind of work that I find particularly exhausting.
The honest version is also: I have spent enough years with one framework that I have started to notice the cognitive cost of it, the way a musician eventually notices the weight of a guitar strap. Not a deal-breaker. Just a thing that is there. The vanilla version had no strap.
But I don’t want to oversell this. The vanilla version had costs too. They were just different costs. It is a question of which costs you can pay, and the answer is “it depends,” and “it depends” is the part of the discourse that nobody wants to hear.
What is actually lost
I want to name, fairly, what you give up when you skip the framework, because the “vanilla is fine” crowd tends to underweight this.
State synchronization. A framework’s main job, in 2026, is not the DOM. It is keeping the in-memory state, the URL, the network, and the rendered view in agreement with each other. The amount of code that does this in a modern app — and the amount of bug surface area it covers — is large. If you write it by hand, you write more code. The code is not hard, exactly, but it is finicky, and the finickiness is a real cost.
Hiring. If someone joins the project in eighteen months, they will need to know what they are reading. Vanilla JavaScript is more readable than people say — until the project has its own ad-hoc state conventions, at which point it becomes less readable than a framework, because a framework has conventions the new person already knows. The vanilla version has conventions only I know. This is a tax I am imposing on a future version of the team.
Ecosystem effects. A framework gives you a thousand small wins — a router that works, a build tool that is configured, a testing setup that is documented. The vanilla version has to reinvent each of these, in miniature, with less testing. The cost of each reinvention is small. The cost of all of them together is not small.
These are real. I am not going to pretend they aren’t.
What you get
What I got, in exchange, was a piece of software that I can hold in my head. I know what every line does. I know what the failure modes are. I can read the whole thing in an evening and find a bug in a place I haven’t visited in a year. I do not have to think about the framework’s release schedule. I do not have to think about a major version migration. The thing will work in five years if the browser still exists.
This is a real benefit, not a nostalgic one. There is a version of engineering where the goal is to make the system easy to change, and there is a version where the goal is to make the system hard to need to change, and a small piece of software, written carefully, is much closer to the second. The framework is on the first team. The vanilla tool is on the second.
Whether you want to be on the first team or the second depends on what the software is for.
The non-transferable lesson
Here is the part of this post I keep coming back to: I do not think you should write your next app in vanilla JavaScript. I think you should occasionally write a small thing in vanilla JavaScript, and notice what the framework was doing for you that you weren’t aware of, and notice what the framework was doing to you that you weren’t aware of. Both of these noticing exercises are useful, and they cancel out into a better-calibrated sense of when the framework is the right call.
The reason I am writing this is that the version of the discourse I encounter most often is the framework-people version, where the framework is the air and the question is which air. That version is fine, mostly. But the inverse version — where the absence of the framework is the default and the framework has to justify itself — is also a useful version to visit periodically, and we don’t talk about it enough.
That’s it. That’s the post. The tool shipped, the client is happy, and I am going back to the framework for the next thing, but I am going back to it on purpose, which is a better way to go back to it than the way I usually do.