Before diving into this article, I have to say that I haven't been a front-end developer for a very, very long time 😁 I just analyze architectures ...
Some comments have been hidden by the post's author - find out more
For further actions, you may consider blocking this person and/or reporting abuse
The
filterexample was especially interesting to me. 😸Your hand-written
useMemohad exactly the cache boundary you wantedproductsandquery. But the compiler grouped the filter into a larger cache block that also depends onpicked, so selecting a row causes the filter to run again.That feels like a really good example of the limit of compiler inference.
ReactCompiler can remove a lot of manual memoization, but it also proves why manualuseMemois still useful when we need precise control over the cache boundary.I’ve read quite a bit of
React’s internals over the years, and I’m also still unsure about the direction of Server Components. I sometimes wonder whether making the component itself such a central unit of server work was the right trade-off. The fact that Next.js has had to work on making RSC navigation feel faster makes that question even more interesting to me.On the rendering side, I personally think
Vue3 has had a technical advantage over React for quite a while. Its compiler-informed VDOM, patch flags, and increasingly fine-grained update model are very compelling, and Vapor pushes that much further. From a purely technical perspective, I often preferVue’s approach.But I completely agree with your point that framework selection is rarely decided by technical quality alone.
SolidandSvelteare obvious examples.Their performance can be excellent, but adoption is harder because fewer developers know them. Even between
VueandReact, I’ve been involved in cases whereReactwas chosen despite preferringVuetechnically, because hiring, ecosystem size, existing team knowledge, and long-term maintainability mattered more.What I think is changing now is AI.
As AI-assisted development gets better, “our team doesn’t know this framework yet” may become a much smaller problem. That could make it easier to choose technologies based more on what we actually want to use.
But it also creates a completely new selection criterion:
A technically excellent library may still be difficult to adopt if agents don’t know it well, generate outdated patterns, or cannot reliably debug it. So I think future technology selection won’t only be about performance, DX, ecosystem, community, and hiring anymore.
We’ll also have to consider how well AI can support the stack.
I’m actually working on a
Nuxt→Reactmigration right now.Reactwasn’t chosen because I think it is technically better thanVue. We chose it after considering the future team, the skills of the existing members, and the development speed we can get with AI.That difference between “the technology I think is technically better” and “the technology that makes the most sense for the organization” may become even more interesting in the AI era. 🐱
Thanks for this 😊
The AI angle is one I didn't have in the article, and my guess is it mostly helps React, since how well an agent knows a framework follows how much code exists for it. Vapor is a small example of the flip side, it's new and it drops the Options API, so I'd expect agents to keep writing code it doesn't support for a while.
Yeah, that was actually one of the reasons I chose
Reactfor the migration. AI already has an enormous amount ofReactcontext to work with. 😸And
Vaporis a good example of the opposite problem.Personally, I see Composition API as one of the most important shifts that came with
Vue3, especially for larger applications and TypeScript-heavy codebases. So if I were maintaining an older Options API-heavyVuecodebase today, I’d already be thinking about gradually moving toward Composition API regardless ofVapor.Still,
Vaporraises a new problem: even if the framework direction is clear, AI agents may keep generating olderVuepatterns thatVapordoesn’t support for quite a while.Anyway, I’m really curious to see where
Vaporgoes from here 😸Agreed, moving to Composition API pays off on its own, and it also leaves the codebase a lot closer to trying Vapor later! And by the way, good luck with the Nuxt → React migration 😊
Nazar, really enjoyed this one, especially that you ran the real compiler output instead of trusting the diagrams.
On the point about keeping an existing codebase, React Compiler doesn't have to be all or nothing. With
compilationMode: 'annotation'and the"use memo"directive, you can adopt it one component at a time and widen it as you get confident.The catch is that components the compiler can't handle get skipped instead of failing the build, so "compiler enabled" doesn't mean every component is optimized. The Memo badge in React DevTools shows which components were actually compiled, and the ESLint plugin catches violations of the compiler's rules.
Thanks! 😊 I only ran the compiler on one small file for the post, so I don't know how it goes on a real app. Have you tried it anywhere? If you rolled it out with annotation mode, how much got skipped on the first pass, and what was the usual reason?
I haven't tried React Compiler on a real app yet, so I can't give you a real number for how much gets skipped. I was going off the incremental adoption path in the React Compiler docs.
From what I've read, components can be skipped when the compiler finds things it can't safely optimize, such as violations of the Rules of React, unsupported patterns, or incompatible libraries. The ESLint plugin surfaces those diagnostics, so that's probably where I'd start before putting a number on how much a real codebase would need to change.
Fair enough, same boat then 😄
If either of us tries it on a real app, the compiler's logger option prints every function it skips along with the reason, so that would give the number pretty fast.
@shubhradev I went back to the example from the post and ran the version with the hand-written useMemo through the compiler too!
With the useMemo left in, the compiler kept my boundary, so the filter only re-runs on products or query. Without it you get the bigger block keyed on picked from the post.
Then I dropped query from the deps array on purpose and the compiler skipped the whole ProductList with "Existing memoization could not be preserved". The build didn't complain, ProductRow still got compiled, and I only saw it because I had the logger on.
It's one small file, so I might be reading too much into it, but my guess is that stale deps arrays would be a big part of what gets skipped in an older codebase. So I think you're right about starting with the ESLint plugin, it would've flagged that missing dep right in the editor, long before I went digging through logger output.
Thanks for going back and running that, Nazar. The missing-dep case is a good one to see, since the build stays quiet and the component just gets skipped. That's a solid argument for turning on the ESLint plugin first. The logger tip is useful too, so I'll use it if I try this on a real app.
Really enjoyed this comparison! Thanks for taking the time to dig beyond the usual "React vs Vue" arguments and look at how both ecosystems are evolving with compilers and reactivity. The point that the two are converging technically while still having very different mental models was especially interesting.
Great read!
Thanks, glad you liked it! 😊
I ended up with both in one product. Vue on the web, React Native on the phones, and I write both.
What costs me time switching between them is where state is allowed to change. In Vue I mutate a ref and the screen follows. In React I catch myself doing the same thing and then spend a while on why nothing re-rendered.
So your mental model section matches what I see more than the compiler part does. The output converging does not help my hands much.
Did the comparison use production builds with the same update trace? That would separate compiler output from runtime behavior.
Nope, there's no runtime benchmark in this one.