Skip to main content
5 min read

Do We Actually Need Modern Frameworks?

I'm really starting to wonder if modern frameworks are creating more problems than they're solving.

frontendjavascriptreact

I started out building websites in PHP back when every click resulted in a full page refresh. Around the time I graduated college and started hunting for my first real job, the internet was finally ready to move towards interactive websites that used JavaScript instead of Flash. And I fell in love with frontend web development.

In those days, we used libraries like jQuery to build interactivity by hand. No components, no data binding, no render cycles. The server would return some HTML, and you would write JavaScript to manually go in and modify the DOM. Today, that sounds crazy, but I'm really starting to think it was far easier than modern web development.

My first experience with modern frameworks

The first time I worked with modern framework practices was shortly before Angular came out. Initially, it felt like I was giving up too much control to a mysterious render cycle. But it was an exciting new way to write web apps, so I learned to love these patterns. At the time, I was using a custom setup built around KnockoutJS.

Next, I moved over to Angular. I was blown away by how simple it felt to use, and how powerful components were. But there was always a major pain point for me: when you give up control to a render cycle, you have very little control over things like transitions between views.

This bothered me at the time: I went from building applications with smooth animations and transitions to ones that just changed with no smoothness. Over time, I got used to this, but thinking about it now, it just feels like we lost something important.

Example 1: Dropdown Animation

Let's look at a simple dropdown, for instance:

dropdown-jquery.ts
$('.toggle').on('click', () => $('.dropdown').toggleClass('open'));

In React, even the equivalent approach requires introducing state.

dropdown-react.tsx
const [isOpen, setIsOpen] = useState(false);
 
return (
  <>
    <button onClick={() => setIsOpen((o) => !o)}>Toggle</button>
    <div className={`dropdown${isOpen ? ' open' : ''}`}>{/* content */}</div>
  </>
);

The result on screen is identical. The work to get there is not.

This is just the simplest example, where the code is just uglier. Doing more complicated animations (such as chaining jQuery animations) is virtually impossible with modern frameworks.

Example 2: Stale Closures

Having a stale value because the event handler was set during an old render cycle. Take setInterval as an example: if you create one inside a useEffect and reference a state value, it will forever read the value from when the component first mounted, because the closure captured it then.

interval.tsx
const [count, setCount] = useState(0);
 
useEffect(() => {
  const id = setInterval(() => {
    console.log(count); // always 0, no matter how many times count updates
  }, 1000);
  return () => clearInterval(id);
}, []);

To fix it, you need would need a ref to hold the current value (which only really works for this over-simplified example), or you would need to add count to the dependency array (which would break the setInterval timing entirely). Neither solution works. (There are working solutions to this problem, as explained in Making setInterval Declarative with React Hooks by Dan Abramov)

Example 3: Stabilizing References

An infinite re-render because of a useEffect call with a circular dependency. And sometimes the problem isn't a bug at all. It's just code you're forced to write to appease the render cycle. useCallback and useMemo often have nothing to do with performance optimization. They exist to give useEffect a stable reference so it doesn't fire on every render, and to prevent child components from re-rendering when a parent re-renders.

callback.tsx
const handleChange = useCallback((value) => {
  setFilter(value);
}, []);
 
// Without useCallback, this fires on every render
useEffect(() => {
  fetchResults(filter);
}, [handleChange]);
 
// Without useCallback, Child re-renders every time the parent does
return <Child onChange={handleChange} />;

You're writing code whose only purpose is to make other code behave!

And the crazy part is: the apps we're making really aren't any more complicated than they were before modern frameworks!

So why do we use them?

One major thing that's changed since then: we used to have separate frontend and backend engineers. Now, almost every company relies entirely on full stack. And modern frameworks make this much easier.

But it always felt wrong to me. It reminds me of some of my freelance work. I'm a very good engineer and an okay designer. But freelance clients would ask me to do design and development. I tried several times to talk them out of it. "You know my rate is based on my programming skills, but you'll be paying it for my design skills, which are worth far less." I never successfully talked a client out of it though.

It feels like that. Every full stack engineer I've met has been far better at one than the other. So why would you pay two salaries for two people who are okay at both things when you could pay one of each the same money to be great at their specialty?

I want to test my theory

I'm thinking that I need to build something substantial without using a modern framework. Something that relies on traditional DOM manipulation. I want to find out if it really does feel easier than modern frameworks. I suspect it will, but with one major caveat: it requires a lot more planning to make it work.

I'm also very curious if an option like HTMX and/or Astro might be the middle ground that lets me feel in control again, but without having to write my own framework from scratch. But I'm leaning towards the first option. And along the way, I could look for the gaps that need filling, and maybe build a new minimal framework to add some structure to traditional patterns.


Copyright © 2026 Robert Messerle.

All rights reserved.

...Or maybe some rights reserved? Who's to say?