React 18 & Next 13: Love at first render?

Engineers have very strong opinions about their tools. If you're in frontend engineering, you know everything moves fast and it's in your best interest to at least care about the new stuff.

Some engineers believe there was nothing wrong with the traditional multiple-page applications and React shouldn't even exist. It's easy to agree with this point, but the facts are: no one can deny the developer experience baked into these new tools.

In this article, I'll try to put together my random thoughts on React server components and how it forms the basis for the next app router. I'll try to answer the following questions...

  • Why do we need RSC
  • How does it differ from SSR

This article is for people that don't get it. I'll try to go over the problems of client rendering, and why basic server-side rendering might not solve all of them. Let's gooo!

Where it all started

The traditional way of building webpages was simple and easy to reason about. The client made a request to the server and it responded with HTML rendered on the server. New requests to the server would always prompt a full browser reload and HTML response from the server. Let's imagine an ECommerce site...

  • User requests product page
  • Server responds with product page (with just a few reviews)
  • User clicks on more reviews
  • Server responds with another page with all the reviews

Traditional MPA architecture

It was quite annoying and we quickly figured we needed a way to update parts of the webpage without full reloads. This was the birth of Ajax, single-page applications, and eventually React.

React made it easy to build truly single-page applications.

Single Page Applications

React ships JS bundles that render on the client. But there were 2 performance problems with this approach...

  1. Shipping large bundles of JS before showing anything to the user: When a user requests a website, the server responds with a skeleton HTML file, which in turn loads numerous JS bundles (containing react, other dependencies & application code). Most times, hundreds of kbs of javascript have to be loaded before a user can see anything on the webpage. A lot of these problems can be solved with tree-shaking, app shell architecture and code splitting, etc, but you still had to set it up right.
  2. It's expensive to download, parse and run javascript on browsers: JS is single-threaded, and it can be time and resource-consuming to run all that javascript on the user's browser. It's more difficult to build Performant websites that run at 60fps with all that interactivity that modern web apps require.
  3. No SEO: Rendering on the client meant that your website couldn't be indexed by search engines, making you lose out on SEO.

Rendering on the server

React began to add APIs to support rendering on the server. The idea was to improve perceived performance by rendering on the server, sending HTML to the client, then hydrating with some more javascript for interactivity. Next.js was built on top of this.

SSR improved SEO, FCP and reduced the amount of Javascript shipped. It finally felt like we were solving these problems.

Next.js server-side rendering

But there were also a couple of problems with Next.js, the first being that the React framework didn't provide any solid primitives for rendering on the server. Take, for example, data fetching. Next.js gave us getServerSideProps which can only be used on root pages. While this is SSR, it didn't take the full benefits of having your components on the server. With RSC, each component can have full access to the server resources (DB, etc).

The second was the heavy js bundle required to rehydrate the page after the initial HTML. Of course, the page was rendered on the server, but it needs some javascript for interactivity.

These were some of the problems React 18 was made to solve.

React 18: React server components

Our latest major version includes out-of-the-box improvements like automatic batching, new APIs like startTransition, and streaming server-side rendering with support for Suspense. - React docs

React 18 takes concurrency to another level by adding components that are rendered on the server and streamed to the client. This can solve most of the problems we experienced with Next.js. They also have full power over the server - directly making API requests, accessing the database, etc. This solves the problem of having to make one large API request in getServerSideProps and the network waterfall that comes with it. Now all server components can make their own request and stream the serialized data to the client when they're done.

This also solves the problem of shipping large JS bundles. RSCs do not need to be hydrated like traditional SSR components. Whenever they need to refresh, they contact the server and rerender with the new data. This whole experience is baked with Suspense and there's always a fallback when data is been fetched.

Next 13: App directory

Next.js 13.2 shipped with a beta version of the app router. Components put here are server components by default (Meaning they use React Server components). This is the first major production implementation of RSC.

To make the transition to Server Components easier, all components inside the App Router are Server Components by default, including special files and colocated components. This allows you to automatically adopt them with no extra work, and achieve great performance out of the box. You can also optionally opt-in to Client Components using the 'use client' directive - - Next.js docs

This version also ships with other interesting features such as Layouts, Streaming, etc. Next.js takes this to another level by adding a use-client directive to delineate between server and client components. Client components are pre-rendered on the server and hydrated on the client (The usual way SSR works in the pages directory).

Next.js manages to do this by having two graphs - the Client Component Module graph and the Server Component Module graph. It then knows where to put what based on the directive you give the label the component with.


React server components sound great on paper, but I'm yet to try that out. I plan to rewrite one of my side projects with RSC (with the new next.js app router). Hopefully, I'll learn more about it and can give a full-circle review. But I also have the following questions...

  • Why do emotion and other CSS-In-JS libraries not work with RSC out of the box
  • What other frameworks are going to adopt RSC
  • Are RSC actually worth the migration

Until next time!