Two ways to the two Reacts
#Reactjs #webThe upcoming release of React 19 and the introduction of the full-stack React architecture are doubtless the most consequential shifts in the recent React ecosystem. Before, React primarily lived on the client side. Yes, it provided ways to run the initial rendering on server and hydrate on client to meet the needs of the community—SSR in short. But this is more a workaround and an optimization trick than a core tenet of the original React model.
React 19 changes this equation by bringing its component model to a new environment. React now understands components placed on the server environment (“RSC is React Server + Component”). But what does that mean for building React apps? Dan Abramov has posed this very question in a blog post “The Two Reacts”: how should we combine React Components on both sides? Dan later presents his answer in his ReactConf 2024 talk “React for Two Computers” and paints a vision where two computers separated by time and space work together. Key to this vision is the unidirectional flow of computation—the server starts the work for the client to complete.1
The de facto reference implementation of this vision is, of course, the Next.js App Router. Given the close collaboration between the React and Next.js teams for its development, one may even argue that the App Router is what the new React architecture should look like.2 The impending 1.0 release of TanStack Start challenges this narrative. Instead of moving its foundation to the server side, a TanStack Start app maintains the familiar client-oriented model.
Server as the new foundation
Next.js App Router flips the client orientation of React. Server is no longer an afterthought but a starting point. Only by marking a component with “use client” can we leave the server environment to enter into the client environment. This boundary can be placed anywhere in the component tree, but we can only travel from one side to the other, from server to client. From the official Next.js docs:
When working in these environments, it’s helpful to think of the flow of the code in your application as unidirectional. In other words, during a response, your application code flows in one direction: from the server to the client. […] this model encourages developers to think about what they want to execute on the server first, before sending the result to the client and making the application interactive.
Not only is it a starting point, but server is also where most of the component tree should live. Keep as much data as possible on server, render as much as possible on server, reserve client for for necessary interactive logic only. In other words, move client components down the tree! The benefit is clear—accessing server resources and data is made simple and the client bundle lighter. But like everything else, it’s a tradeoff. Component in React is a unit of computation to convert data into UI, so keeping the bulk of component tree on server means more work on server. Plus, components on server are now directly responsible for data fetching, so naively rendering them suffers from network latency.
A proven answer to both saving computation and reducing latency is caching. It took some time and iterating, but "use cache"
is a culmination of this new server orientation. This journey started with “extending” the fetch
Web API in the beta App Router for Next.js 13, then unstable_cache
in the stable App Router for Next.js 14. Despite the good intention, the first was confusing and the second was cumbersome to use. But the third time’s the charm, and the new directive-based solution feels more composable and convenient. All the while, the overarching theme has remained unchanged: cache lives on server. Because work and data live on server.
The evolution of Next.js App Router shows that the original vision for two React is finally coming to its completion. The unidirectional flow of data and UI from server to client, now more ergonomic and efficient than ever. Server is the new foundation.
Server as another feature
Note: This section is largely based on Tanner’s excellent talk at 2024 Netlify Compose, “An Early Glimpse of TanStack Start”. Definitely give it a watch to get the full picture.
But the story doesn’t end there. Not everyone wants to do a 180 on how to build and structure React apps. There is a real cost of making that shift and the server orientation of App Router may not even be a good fit for certain apps and their requirements. Enter TanStack Start.
Following the example of its “cousin framework” SolidStart, the new TanStack framework takes an incremental and layered approach to building full-stack React apps. In Tanner’s own words:
TanStack Start has a very simple mission. Deliver the best client-side authoring experience possible, and then layer on powerful and flexible server-side primitives that don’t sacrifice the performance or your developer experience.
The layers of this full-stack TanStack look like the following: TanStack Query is a proven solution for managing async server state for React apps on client. TanStack Router (indirectly) builds on this to offer type-safe, URL-driven application state management.3 And TanStack Start, again, builds on this to add support for server features such as SSR and hydration, streaming, server functions, and more. In constrast to the all-or-nothing binary choice of Next.js, this Start approach allows greater flexibility and gradual adoption.
When it comes to supporting full-stack React in particular, TanStack Start intuits that server component and server function are nothing special. Behind the veneer of novelty, they’re just another form of handling server state for your React app. This intuition has led to the API design where a server function returned from createServerFn
can be used as a loader
in TanStack Router or a queryFn
in TanStack Query with the same excellent caching on client—TanStack’s bread and butter.
Although TanStack Start has not committed to server component support for its upcoming 1.0 release as of this writing, we know it is coming. We can catch the glimpse of what it’ll look like from the aforementioned talk and from a bit of digging through the current codebase. There, we find a server function that returns a React element, which then gets rendered into the final React tree on client. Server component in TanStack Start will be just a different type of async data to manage, so we can rely on Query and Router for the same excellent client-side caching.
If the original formulation of React for Two Computers flips the old client orientation of React, TanStack seeks to flip it back. In this pendulum swing, we get server as a feature along with the client orientation we’ve learned to love.
Two are better than one
Between Next.js App Router and TanStack Start, there is no one right answer. Instead, they are a gift to the React ecosystem, which can now enjoy two distinct approaches to exploring the new React architecture. Thanks to Vercel’s unwavering investment in the framework and the supporting infrastructure, the new server-first React with Next.js seems truly ready for its prime time. And while TanStack needs more time to fulfill its mission, TanStack Start presents an alternative path toward the renewed client-first React that will surely inspire other players in the space such as React Router/Remix.
The full-stack vision for React makes a case that the two Reacts are better than one. Two ways to the two Reacts definitely feel better than one. I’m bullish on full-stack React in 2025.
Footnotes
-
It must be noted that Dan’s blog post and talk are only a reformulation of what React team has gone through over the last few years. Still, they are a great resource to retrace that journey. ↩
-
Waku is another framework in development that implements the React architecture. I’m only mentioning it in a footnote because 1) it has not reached the v1 release yet and 2) it more or less follows the same Next.js App Router approach. ↩
-
From official TanStack Router docs: “[TanStack Router’s] caching layer is loosely based on TanStack Query, but with fewer features and a much smaller API surface area.” ↩