The Fatigue of Infinite Configurability
We demanded flexibility, and we got it. Now every component is a shapeshifter, accepting a dozen props to define its existence. A look at why constraints are better than options.
There was a time when starting a project meant writing an HTML file. Maybe, if you were feeling ambitious, a CSS file too. Today, starting a project is an exercise in bureaucracy. Before you write a single line of domain logic, you must adjudicate a dozen architectural treaties between your tools.
Which bundler? Which linter? Which formatter? Which CSS-in-JS solution (or utility class compiler)? Strict mode or loose mode? Paths alias or relative imports? The modern frontend developer is less a craftsman and more a diplomat, negotiating peace treaties between hostile dependencies.
We demanded flexibility. We screamed for it. And the ecosystem answered. Now we are drowning in it.
We have mistaken the ability to configure everything for the power to control anything. In reality, every config option is a leak in the abstraction.
The Config Explosion
Open the root directory of any modern JavaScript project. Count the dotfiles. .eslintrc, .prettierrc, .babelrc, tsconfig.json, vite.config.ts, tailwind.config.js, postcss.config.js, jest.config.js, next.config.js... the list is a scroll of infinite sorrow.
Each of these files represents a mental branch. Each line in them is a decision that someone had to make, and more importantly, a decision that someone else has to understand later.
We often talk about "Cognitive Load" in terms of complex business logic. But we ignore the cognitive load of the environment itself. When a developer cannot simply "run the code" without understanding how three different transpilers interact, we have failed.
The Cost of Options
Every configurable option in a library is a point of failure. It is a variable that can interact with other variables to create states the author never tested.
I recently spent four hours debugging a build failure. The cause? A conflict between a Babel plugin and a TypeScript setting, strictly related to how optional chaining was transpiled when a specific compile target was set. This is not engineering; this is trivia. It is knowledge that has zero value outside of the specific, ephemeral context of this specific stack.
"But what if I *need* to customize the AST transformation phase?"
Do you? Do you really? Or do you just want the *possibility* of doing it, just in case? We hoard options like survivalists hoard canned beans, preparing for edge cases that never arrive.
Constraints as Features
There is a liberating feeling in using a tool that says "No." Go (the language) is famous for this. No generics (for a long time), no exceptions, one way to format code. You don't argue about style. You don't configure the linter. You write code.
In the frontend world, we look at constraints as limitations. We see a framework that mandates a file structure and we chafe. "Don't tell me where to put my components!" we cry, while creating our fifth unique directory structure of the year.
But constraints are features. They remove the burden of trivial decisions. When the tool makes the choice for you, your brain is freed to think about the problem you are actually trying to solve.
Embrace the Defaults
My new rule for tooling is simple: If it requires more than five lines of configuration to start, it is too complex.
- Use standardized toolchains (Next.js, Vite) and accept their defaults.
- Delete your custom ESLint rules. The standard config is fine.
- Stop fighting the platform. If a tool fights you, drop the tool, not the platform.
We need to stop celebrating the "highly configurable" and start celebrating the "highly opinionated." I don't want a tool that can do anything. I want a tool that does one thing so well that I forget it's even there.
Let's make web development about the web again, not about the config files that build it.