A Look Back at Simpler Times

Front-end development in the early 2000s had a certain charm to it. Static HTML pages, basic CSS, and a sprinkle of JavaScript — often jQuery — were enough to build a functional website with interactive elements like sidebars and carousels. That was the standard. Anything beyond that was considered exceptional.

Large, dynamic applications like YouTube and Facebook certainly existed, but they were the work of massive engineering teams. No one expected a small team or an individual to replicate such projects. That was the exception, not the rule.

Of course, those days had their own headaches. Cross-browser compatibility was a constant battle. Developers wrestled with table-based layouts and float hacks. SEO meant little more than adding meta tags and keywords. Accessibility, responsive design, and performance optimization were afterthoughts compared to the rigorous standards of today.

But there’s a key difference: the problems of the past felt finite. The solutions — however hacky — were graspable. Today’s challenges seem to multiply with every new tool that promises to solve the last one.

The Framework Paradox

JavaScript frameworks like Angular and React weren’t born from a desire to complicate the average developer’s life. They were created by Google and Facebook to manage their own massive, complex applications. The core issue is that these enterprise-grade tools have become the default choice for projects of all sizes.

Many developers underestimate the time and effort required to build and maintain a reliable codebase using a modern framework. But the alternatives — vanilla JavaScript’s messy DOM manipulation and jQuery’s inadequacy for large-scale apps — drove many of us toward these frameworks in the first place. As the web shifted from static information sites to dynamic applications, our tools needed to evolve alongside.

Front-end development has arguably overstepped its boundaries by pulling in traditional back-end concerns, but the frameworks themselves aren't the root problem. The problem is that we reach for them without evaluating whether they fit the task at hand.

Why Stacks Grow Out of Control

As frameworks expanded to cover more ground — state management, server-side rendering, static site generation — they inevitably grew more complex. There’s a one-to-one correlation between what a framework promises and the level of complexity it introduces. The result is that tech stacks have ballooned into multi-layered architectures: MEAN, MERN, MEVN, and countless others are marketed as mature, test-proof foundations with high reliability.

But there’s a fundamental mismatch here. The advertised size of a core framework almost never tells the real story, because these frameworks rely on supporting tools and libraries to deliver reliable architectures. And there is no one-size-fits-all stack — the right tool has always depended on your project’s specific needs.

This is where the problem lies. Giant companies need colossal architectures to handle their scale and user base. They have the engineers, revenue, and clarity of purpose to maintain large codebases. Small companies and projects don’t have those resources, and trying to imitate mega stacks only adds unnecessary weight.

The idea that web development is too complex often comes from buying into the belief that we all have the same needs and resources as giant enterprises. Users don’t care — or even know — what stack you use. What matters is a beautiful, functional website that meets their needs. The technology behind it is invisible to them.

This perspective suggests web development isn't inherently getting harder. Rather, developers are perpetuating complexity by adopting enterprise-grade solutions for problems that don't exist at smaller scales.

Frameworks Aren’t the Only Way

Every project is portrayed as needing a JavaScript framework, but smaller applications are often better served by simpler tools. A component-based architecture in a framework can complicate matters, forcing unnecessarily intricate hierarchies on projects that don’t need them.

The idea that we need a framework for everything has been massively oversold. As Edge engineer Alex Russell put it in his article “The Market For Lemons”:

These technologies were initially pitched on the back of “better user experiences” but have utterly failed to deliver on that promise outside of the high-management-maturity organisations in which they were born. Transplanted into the wider web, these new stacks have proven to be expensive duds.

The actual purpose of a framework is to simplify development and save time. If a project is small, the setup overhead and integration effort can outweigh the supposed time savings. Frameworks are invaluable for big, interactive applications, yes, but they can introduce inefficient workflows and technical debt — intentionally or not. That is the very thing we risk adding when complexity becomes a habit rather than a choice.

The honest questions to ask: Are HTML, CSS, and a pragmatic dose of JavaScript enough for what you're building? If so, stick to them. Modern platform features — Flexbox, Grid, the Intersection Observer API, native lazy loading — have solved problems that previously required heavy browser workarounds. The fundamental synergy between modern front-end frameworks and web platforms has never been stronger, yet many developers overlook the simpler path.

The Human Cost of the “Know Everything” Stack

The pressure to adopt framework-heavy architectures doesn’t just burden projects; it weighs on the people building them. Most teams don’t have the resources to maintain such systems. When we imply that any project—regardless of size—requires a bleeding-edge stack, we place the burden of meeting those expectations squarely on individual developers. They are expected to be proficient in everything: server, database, front end, design, accessibility, performance, and testing. This is the root of what Chris Coyier calls the “Great Divide” in front-end development.

“The divide is between people who self-identify as a (or have the job title of) front-end developer yet have divergent skill sets. On one side, an army of developers whose interests, responsibilities, and skillsets are heavily revolved around JavaScript. On the other, an army of developers whose interests, responsibilities, and skillsets are focused on other areas of the front end, like HTML, CSS, design, interaction, patterns, accessibility, and so on.”

— Chris Coyier

Under these expectations, developers who specialize in HTML, CSS, design, and accessibility can feel undervalued in an industry that seems to praise those focused purely on the latest stack. Terms like “full-stack” and “10x developer” only reinforce this division. Mandy Michael has highlighted the personal toll of this mentality:

“The worst part about pushing the ‘know everything’ mentality is that we end up creating an industry full of professionals suffering from burnout and mental illness. We have people speaking at conferences about well-being, imposter syndrome, and full-stack anxiety, yet despite that, we perpetuate this idea that people have to know everything and be amazing at it.”

— Mandy Michael

This chase for the latest tooling also warps the learning curve. It’s become common to find developers who are highly proficient with a particular framework but know little about the underlying platform, such as HTML semantics and structure. This isn’t a new concern; Rachel Andrew pointed it out back in 2019:

“That’s the real entry point here, and yes, in 2019, they are going to have to move on quickly to the tools and techniques that will make them employable, if that is their aim. However, those tools output HTML and CSS in the end. It is the bedrock of everything that we do, which makes the devaluing of those with real deep skills in those areas so much more baffling.”

— Rachel Andrew

To be clear: modern JavaScript frameworks and libraries are not inherently bad. They simply aren’t designed to replace the web platform and its standards, even if we often push them as if they should.

The Expanding Reach of Vendor Lock-In

Vendor lock-in occurs when a project depends so heavily on a proprietary product that switching to an alternative becomes nearly impossible. This has traditionally been a back-end concern, particularly with cloud services like AWS or Firebase where migrating databases is costly and time-consuming. However, vendor lock-in is now creeping into the front end via meta-frameworks.

As companies behind certain meta-frameworks begin offering their own hosting services, swapping hosts is becoming harder. Teams naturally gravitate toward the hosting service provided by the company that built their framework—they are the experts, after all—but this increases the project’s dependency on that specific vendor.

Next.js and its relationship with Vercel is a clear example. Since the launch of Next 13, self-hosting became increasingly difficult. This led to community projects like Open Next, which explicitly notes that while Vercel is great, it’s not ideal if your infrastructure is on AWS. Fortunately, Next 14 brought clarity on self-hosting a Next app on a Node server.

Gatsby offers a similar case. While Gatsby has always provided guides for alternative hosting, the introduction of Gatsby Cloud in 2019 optimized the main framework to work seamlessly with its proprietary cloud service. Integrating Gatsby with other hosts became harder, as if you are penalized for exercising choice. The problem was compounded when Netlify acquired Gatsby Cloud in February 2023; some teams reported costs jumping 120% after being forced to convert to Netlify, even on identical plans.

The common answer to this problem is to abandon paid cloud services for open-source alternatives. While viable for some, this ignores that an open-source project may not meet an application’s specific requirements. Open-source software also relies on community maintainers who often work for little or no pay, and it is equally prone to locking you into solutions designed to fix a specific deficiency.

Some dependencies, like React, have an engaged community that guarantees longevity. But you cannot have that same assurance with every new package you add. We cannot keep installing more components every time we spot a weakness, especially when a project would be better served by a simpler architecture that properly leverages the web platform.

“Choosing technology for your stack is an exercise of picking your own poison. Either choose a paid service and be subject to vendor lock-in in the future, or choose an open-source one and pray that the community continues to maintain it.”

For many teams, these are the only two choices. Most depend on third-party services because developing their own infrastructure is a luxury only massive companies can afford. The goal, then, is to minimize the number of dependencies and choose wisely when adding them is unavoidable.

The Development Paradox

Why have modern development stacks grown so large? Part of the answer lies in a feedback loop that can be called the “Development Paradox.” Each new framework or library is created to solve a problem, but it inevitably introduces new problems of its own. Developers spend months building new tools to solve those follow-up issues, and when there isn’t a problem, one is often created anyway. This loop produces amazing technology, but it can also lead to severely over-engineered websites.

React illustrates this dynamic well. Originally created by Facebook for Facebook to handle dynamic user features, it was open-sourced in 2013. Since then, hundreds of utilities have emerged to solve React-specific problems. Need to start a project? There’s Create React App and Vite. Need state management? Redux and others. Need form handling? React Hook Form. Need server-side rendering? Next, Remix, or Gatsby. Each of these solutions brings its own caveats, sparking further development.

It may seem unfair to single out React since it considers itself a library rather than a framework, making community extensions inevitable. But Angular and Vue have their own sprawling ecosystems, and they represent just the tip of the iceberg in a sea of JavaScript frameworks, each with its own ideology and dependencies.

New technologies and options are liberating. But when building something as straightforward as a small website, we must draw a line that defines how many new technologies we use and how reliable they are. We are mash together third-party code written by various third-party developers, and the complexity adds up quickly. Users don’t care what is in our stacks—they only see the final product. Knowing this can free us from working on unnecessary architectures and locked dependencies that are only appreciated within development circles. Sometimes, the best solution is not to fix what isn’t broken.

Scope Decisions Are Where Complexity Creeps In

If complexity isn’t forced on us, why does it keep showing up? One reason is the automatic pairing of a technology with a feature. Adding toast notifications to a React app, for example, immediately surfaces a library like react-toastify, when in reality the feature might be entirely optional. The first question shouldn’t be “which library do we use?” but “do we need this feature at all if it adds dependencies?”

Consider a simple use case for an app that lets users find, review, and rate nearby restaurants. The minimum viable product is clear: restaurant data, a search function, and registration with secure authentication. Everything else — SSR, notifications, offline support, or elaborate animations — is a guess about future needs. Too often, those guesses delay delivery before the first real user has even registered. This is scope creep, and it turns reasonable products into over-engineered ones that may never launch.

The core problem isn't a single new technology but the accumulation of personal preferences and assumptions that get bundled into the project’s scope.

When we do have control over a project, the hardest requirement is to recognize when complexity is becoming unnecessary. There’s no reliable rulebook here, since requirements are mostly assumptions. Some assumptions are safe, like a login flow. Others are territory-dependent, say whether users need private messaging. Many more are speculative, like assuming a shopper will notice sub-100ms latency on a product page. The challenge is telling the difference.

  • Can we cover the styling needs with CSS modules, or does that require a preprocessor or full framework?
  • Is vanilla JavaScript acceptable, or is TypeScript adding real value to the codebase now?
  • For rendering, do we truly need SSR, SSG, or a hybrid, or is one approach overkill for the requirement?
  • Is Redis for faster backend queries a feature for the users or a preference for the developers?
  • Between unit tests and end-to-end tests, which one covers the risks that actually break this app?

These are legitimate considerations, but they can easily shift the focus from shipping to architecture theater. The guiding principle that applies most often: done is better than perfect. Even the largest applications started as small, iterative releases.

“Done is better than perfect.”

— Sheryl Sandberg

Two questions help avoid unnecessary additions. First, what happens if this feature or dependency is left out? If the answer is “nothing,” it’s probably not a core feature and should be deferred. Second, why are we adding x anyway — because it’s fashionable, or because it removes a genuine obstacle for the main user story?

There’s also a case for preferring tools you already understand. Familiarity can trump a popular framework that looks clever but forces a learning break mid-development. The right tool for the job may simply be the one you can apply immediately and maintain over time, rather than one advertising best-in-class scalability that your single-purpose app hasn’t proven it needs.

Complexity Isn’t the Same as Over-Engineering

So, is this an issue of the web platform getting inherently more complex, or an issue of self-inflicted additions? Nearly all the complexity we criticize in day-to-day development is self-inflicted, made up of unnecessary libraries and speculative architecture. While the underlying platform presents its own constraints by nature, the over-engineering we see is more often a delivery problem than a foundational one. Making conscious stack selections that match the immediate product, instead of anticipating every potential trend, remains the most direct way to start shipping less crowded software.