One framework, few dependencies: stack discipline that compounds
--- title: "One framework, few dependencies: stack discipline that compounds" published: true description: "Fewer dependencies is not zero dependencies. How one framework ecosystem and the web platform, with a few earned
---
title: "One framework, few dependencies: stack discipline that compounds"
published: true
description: "Fewer dependencies is not zero dependencies. How one framework ecosystem and the web platform, with a few earned exceptions, keep a codebase cohesive."
tags: nextjs, react, security, webdev
canonical_url: https://brokeinprod.dev/full-stack/one-framework-few-dependencies-stack-discipline
---
The most consequential decisions in this build were not the things I added. They were the things I declined to add.
Start here: Pick one framework ecosystem and lean on the web platform (native
fetch,FormDatawith server actions,crypto.randomUUID,Intl, Tailwind utilities) before reaching for a library. Then allow a few deliberate exceptions that close a real gap you would otherwise implement worse: image processing, MDX, an email SDK, accessible drag-and-drop. The goal is not zero dependencies, which is its own failure mode. The goal is that every dependency earned its place. Cohesion compounds: one mental model, fewer version conflicts, smaller bundles.
The setup
By the time this build had a blog engine, an admin panel, image processing, email, and rate limiting, the package.json could have ballooned. It did not, and that was not an accident. It was a rule applied at every "should I install something for this?" moment. The rule has two halves: commit to one framework ecosystem, and make every dependency earn its weight.
Half one: one ecosystem
Next.js is React's meta-framework, so "React or Next" is not a choice between two ecosystems. It is one. The discipline is not bolting a second UI paradigm onto that: no adding Vue for one widget, no Svelte for a chart. Not because those tools are bad, they are excellent, but because cohesion is a feature. One set of idioms, one mental model, one rendering story, one set of version constraints. Every additional paradigm multiplies the surface area a future maintainer (often you) has to hold in mind.
Half two: lean on the platform first
A surprising amount of what people install, the platform now does natively. In this build:
-
fetch+FormDatainstead of an HTTP client and a form library. Server actions take aFormDatastraight from the request. -
crypto.randomUUID()instead of a UUID package. -
Intlfor date and number formatting instead of a date library. - Tailwind utilities instead of a component-styling runtime.
None of these needed a dependency. Reaching for the platform first is the cheapest way to keep the tree small without writing anything clever.
The exceptions that earned their weight
"Few dependencies" is not "no dependencies." That is the opposite failure, where you reimplement crypto, image processing, or accessible drag-and-drop yourself and do it worse. The test is whether a library closes a real, hard, well-solved gap. The ones that passed:
- Sharp. Image resizing and format conversion wrap libvips. You do not hand-roll this.
-
next-mdx-remote. Compiling and rendering MDX with embedded components is involved. - The Resend SDK. Thin, but it makes the email API ergonomic and typed.
- The drag-and-drop library. Accessible reordering (pointer, keyboard, and screen-reader semantics) is deceptively hard and easy to get wrong.
Each one replaced something I would have built badly. That is the bar.
The rule earned its keep at the install prompt, not in a design review. The moment that tests it is the one where the package is two keystrokes away and the platform version is two hours away.
The honest counterpoint: Astro islands
There is a real, deliberate case for mixing frameworks, and it is worth being fair about it. Astro's islands architecture lets you run React, Vue, Svelte, and others on the same page, each as an isolated island, so you can pick the best tool per component, migrate a site incrementally, or avoid lock-in. Notice how: the mixing is intentional and isolated, with a performance model that makes it cheap, and Astro's own docs note that most developers still stick to one framework anyway. That is the distinction. Deliberate, isolated islands are not the same as mixing paradigms by default inside a single SPA. The discipline is not "never combine." It is "combine on purpose, never by drift."
Why it compounds
A cohesive stack pays off slowly and then all at once: faster onboarding, fewer "why are there two ways to do this" moments, fewer transitive-dependency security advisories, fewer version-conflict afternoons, smaller bundles. None of it shows up in a demo. All of it shows up six months later.
Pick one ecosystem and go deep. Add a dependency only when it closes a gap you would otherwise fill worse.
FAQ
Isn't "fewer dependencies" reinventing the wheel?
No. The rule is justified dependencies, not zero. Reimplementing crypto, date math, or accessible drag-and-drop yourself is the opposite failure. Use the platform where it is capable, and use a library where it closes a real, hard gap.
Doesn't Astro prove that mixing frameworks is fine?
Astro deliberately supports it via isolated islands: best tool per component, incremental migration, no lock-in. It isolates them on purpose, and even its docs note that most developers stick to one. Mixing by default inside a single SPA is the anti-pattern. Intentional, isolated islands are not.
How do I decide if a dependency earns its weight?
Ask what it replaces (a real, well-solved hard problem versus a few lines of platform code), the maintenance and security surface it adds, and whether it locks you in. Sharp wrapping libvips earns it. A date library for one format string usually does not.
Further reading
- Astro — Islands architecture (the deliberate, isolated multi-framework model): https://docs.astro.build/en/concepts/islands/
- MDN — Fetch API (how much the platform now does natively): https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
-
Next.js — Server Actions and Mutations (
FormDatastraight from the request): https://nextjs.org/docs/app/getting-started/updating-data
Originally published at brokeinprod.dev — field notes from production: the error, the cause, the one-line fix.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.