Both Reacts
client-first and server-first methods of react development2025-02-25This is not a post about "server-first vs client first apps." This is why we need both.
I'll start with establishing my perspective... I am on the maintainer team of waku, so it can be inferred that I seriously believe in the power of RSC and its ability to improve react development. I also make dynamic sports analytics dashboards, which lean significantly towards optimizing for control and dynamic rendering on the client.
My goal here is to tie the different styles of apps that can benefit from each development strategy and why.
Server-First React
React 19 has come with new server features and they're super cool 😎.
RSC is largely an attempt to structure apps around a particular data-fetching model that results in smaller bundle sizes and less responsibility for the client to render. For lovers of astro and nextjs, this mental model is a perfect fit.
Let's call this server-first react. We run the initialization of our app on a server, usually in some sort of SSR style. Then, we create client components (analogous to islands), where we have just a bit of our interactivity. When this fits your app, it's a truly great experience for developers and end users.
This type of development is explained quite well in The two reacts, so I'll continue on to the other side I see.
Some History
React as a project, started with SPAs built by CRA. This was the right solution for many projects at the time, but there are large chunks of the web that are more just being shoehorned into a space where they should not be. This is where react-static, gatsby, and then nextjs came into the fold. They solved the unserved market of developers needing blends of SSR/SSG solutions who were crammed into rendering everything in the client for their news publications, blogs, or otherwise before that.
Next.js really stood out as the way to play with different render "modes" and took major market share because of this. Enter all the acronyms: SSR, SSG, and CSR (docs). This solved a ton of mixed needs sites, especially ones that were hosted on Vercel.
With the popularity of Next.js came a trend of the same mis-use that was originally tied to SPAs and CRA. With CRA: the developers of largely static sites were frustrated with their slow to render articles. With Next.js (as the status-quo): App developers became frustrated with unhelpful complexity and docs that simply do not apply to them. I believe that there is a definitive overstep in which apps Next.js are good for. I'd site the countless library issues on Github for "how do i use x with Next.js?", the very vast Next.js docs (which are helpful to many and noisey to others), and cacheing strategies that cause friction in Next 14 (thankfully, this was changed).
I do not want to harp on Next.js, it is a fantastic framework for its intended uses, but I do believe that it can be misused. Here are the two cases I'd like to see use an alternative:
- Apps that should be client-first (try tanstack)
- Apps that play with render modes (ssg, ssr, etc), but do not need fancy cache features (try waku)
Client-First React
This is one of the most popular ways to make react apps.
With the current popularity of Next.js, there is now a world where we have moved the default of the ecosystem to server-first. This may be ok, but many apps that should be client-first are now server-first. Defaults are powerful.
Let's say you're building a customizable analytics dashboard. Your dashboard can have 5 types of widgets: bar charts, line graphs, radar charts, data tables, and a scatter plot. Each can have user-defined metrics: clicks, time spent on page, etc.. Each of these charts are going to have unique needs for the data they fetch and how it is formatted for the base chart component. When users change the dashboard we cannot tolerate a full page refresh. If we were fetching the data for these components in RSCs, this would be a necessary action in its current implementation. We need to enable invalidation and refetching of our data based on client interactions (this is where tanstack/query and others thrive).
Co-location of data fetching with hooks is essential to being able to shape the unique and interactive experiences we want. The fetching is specific to each widget type and therefore best located with the widgets themselves.
Could you attempt to pre-compute all of the widgets for a page and hoist their fetching? Sure. I'd argue that this increase in complexity would become counter-productive and error-prone to a point of it becoming unmanageable though.
A common pitfall of data fetching on the client is called "request waterfalls". The first I heard of the issue and a proposed solution was by the remix/react-router team. Their route loaders are now a common pattern that have been adopted by tanstack router. This is a fantastic solution which hoists data fetching for routes to a single place that will be guaranteed to avoid waterfalls.
Not all data fetching for a route is easily knowable though. The many customizable widgets example is what I am hoping to highlight for this. Co-located data for these extremely dynamic pages is needed for maintainability. And this leads us to another way of avoiding waterfalls. These docs do a great job explaining how fetching from components can lead to waterfalls and how to fix it. This is added complexity to how we develop for the client, but that's the point; we the developers of our apps, are weighing where the complexity should live.
Note: There are many more reasons to go with a SPA (e.g. opting to not run js on a server), but the mental model of developing for the client first is what I'd like to highlight more than anything.
Second Note: The react compiler is set to make client-first app development with react significantly easier to do well. (If you've read this far, you should probably try it!)
Conclusion
RSC and the mental model of fetching first on the server, then creating client interactivity pieces is extremely useful and powerful for many projects. The two reacts explains this perfectly. This simply does not mean that client-first app development is going away though. It is popular for a reason, and the react team is dedicated to improving it with the compiler.
Blending the server and the client in the same code-base has never been easier. Next.js and Waku can make SPAs! And tanstack/start can do SSR! It's up to developers to choose their defaults now... Do you do everything first on the server client components for interactivity? Or do you default to a client-side app with tools for server support?