Skip to main content

React Server Components

Free2021-05-05#Front-End#JS#React服务端组件#RSC#React Server Components vs SSR#SSR with React Server Components#React Server Components under the hood

What are React Server Components? What are they used for? What do they look like?

Preface

Before Christmas 2020, the React team released news about Server Components, and before that, I happened to be researching SSR (Server-Side Rendering), and was amazed by [Next.js's hybrid rendering](/articles/next-js 混合渲染/)

Actually Server Components do have intricate connections with Server-Side Rendering, after all both have Server in their names (serious face, this point is very important). Thanks to previous series of research on SSR, so to some extent, I can more deeply understand the thinking and design considerations behind Server Components

1. What are Server Components?

Server Components are a new type of component that we're introducing to React and this tells React that we only want this component to ever render on the server.

(From Data Fetching with React Server Components presentation video)

Official definition, Server Components are essentially a new type of React component (like Portal, Fragment and other component types), their special feature is this type of component only runs on the server

But, React components run fine on the client, why suddenly say to run them on the server? Haven't figured out Hooks yet, a big wave of server component updates is coming, what are these React team people doing?

2. What Problems Are They Solving?

According to what's written in RFC, React introducing Server Components concept can solve a bunch of problems:

  • Zero-Bundle-Size Components: Bundle size problem

  • Full Access to the Backend: Data access, passing needs to consider component tree structure first

  • Automatic Code Splitting: Code splitting performance optimization needs manual transformation

  • No Client-Server Waterfalls: Request order strongly associated with component tree structure

  • Avoiding the Abstraction Tax: Performance problems brought by multiple layers of abstraction

And so on, but these look more like benefits deduced from the solution, and I'm more concerned about the original intention, what was the first problem they most wanted to solve?

First, we wanted to make it easier for developers to fall into the "pit of success" and achieve good performance by default. Second, we wanted to make it easier to fetch data in React apps.

(From Motivation)

Original intention is to solve two major categories of problems:

  • First category: Performance optimization is relatively complex, can it achieve high performance by default?

  • Second category: There are actually many concerns about fetching data in React applications, is there a simpler, more elegant way?

Performance optimization such as on-demand library references, code splitting by route, advancing data requests, reducing excessive abstraction, etc., these optimization measures all require manual transformation, creating certain complexity for application development. On the other hand, for high performance, usually lift data requests to top level, leading to data display components cannot clearly correspond to data sources

For example what we commonly see:

// 数据展示组件的数据源依赖不清晰
function ArtistPage({ artistID }) {
  const artistData = fetchAllTheStuffJustInCase();

  return (
    <ArtistDetails
      details={artistData.details}
      artistId={artistId}>
      <TopTracks
        topTracks={artistData.topTracks}
        artistId={artistId} />
      <Discography
        discography={artistData.discography}
        artistId={artistId} />
    </ArtistDetails>
  );
}

(For code maintainability considerations) what we want more is:

// 类似这样的清晰依赖,每个组件明确知道其数据从哪来
function ArtistDetails({ artistId, children }) {
  const artistData = fetchDetails(artistId);
  // ...
}
function TopTracks({ artistId }) {
  const topTracks = fetchTopTracks(artistId);
  // ...
}
function Discography({ artistId }) {
  const discography = fetchDiscography(artistId);
  // ...
}

For the React team that constantly pursues ultimate user experience and developer experience, simpler, more elegant data fetching methods is their ongoing exploration direction (such as Suspense for Data Fetching (Experimental)). Therefore, actual situation may be React team was solving data fetching problems, proposed Server Components thinking, suddenly realized this bold idea can also solve many performance problems along the way, thus there's this leap from client to server new feature preview...

3. How Do Server Components Solve It?

To solve component's data association problem, need to let components have their own clear data sources, but waterfall requests bring performance problems... good user experience, low maintenance cost, high performance seem difficult to have all three, but not impossible to have all three, at least React team has explored two solutions:

  • Relay + GraphQL: Relay framework cooperating with GraphQL features, merges scattered data requests, thus solving performance problems

  • Move our components to the server: Move components to server to run, reducing data request costs, thus (to a large extent) solving performance problems

GraphQL can easily assemble data fragments according to requested data model (schema), cooperating with Relay framework to merge multiple requests into one, both preserving component source code maintainability (clear data source dependencies), and avoiding performance problems caused by this, but unfortunately strongly depends on GraphQL, not a truly general solution

While Server Components approach is relatively wilder, to reduce time overhead of multiple client requests, simply put components on server to run, and (same unit) server-to-server data communication is quite fast, at this point performance overhead of multiple data requests is not to be feared, and eventually will be thoroughly solved after (framework layer) introduces data caching mechanism

Wait, moving components to server to run, isn't that SSR? Is this picking up morning flowers in evening?

4. Relationship Between Server Components and SSR?

Connection

Generally speaking, traditional SSR can't avoid two processes, server-side rendering + client-side hydrate second rendering:

  • Server-side rendering: Render first screen content on server (HTML string)

  • Client-side hydrate second rendering: After loading first screen content on client (at this point page is not interactive), and complete code of front-end application, perform a hydrate second rendering process similar to render, bind interaction events on (at this point page is interactive)

Server Components rendering process is similar to this:

  • Server-side rendering: Render first screen content on server (an intermediate form, also used to describe UI)

  • Client-side rendering: Receive intermediate form output from server, render from beginning, start streaming rendering

So, Server Components and SSR connection has at least these points:

  • Both execute component rendering logic on server (so both don't support interaction)
  • Both allow same component to run across client, server (Shared Components)
  • Both extend from client to server seeking more performance breakthroughs

For relationship between the two, React official has a word that's very accurate, Server Components and SSR are complementary, double swords combined, SSR can render first screen as HTML to accelerate content display, Server Components can help reduce code amount needed to load and execute for hydrate second rendering (Server Components only render on server, related code doesn't need to load and execute on client), thereby accelerating page's interactive time:

You can combine Server Components and SSR, where Server Components render first, with Client Components rendering into HTML for fast non-interactive display while they are hydrated. When combined in this way you still get fast startup, but you also dramatically reduce the amount of JS that needs to be downloaded on the client.

Differences

Key differences are 3 points:

  • Server Components related code never goes to client at all, while traditional SSR all component code must be bundled into client bundle

  • Server Components allows directly accessing backend at any position in component tree, traditional SSR only allows fetching data at top level (page level)

  • Server Components can preserve client interaction state during updates (including entered search terms, scroll position, focus, selected content, etc.), because Server Components rendering result is an intermediate format richer in information than HTML (after all HTML can only express HTML, custom format doesn't have this limitation, such as can carry props)

Server Components only execute on server, client doesn't load this code, what server gives to client is always just Server Components rendering results, including second updates, after giving to client in intermediate form, client only merges rendering results from service onto currently rendered client components, so can preserve interaction state:

Specifically, React merges new props passed from the server into existing Client Components, maintaining the state (and DOM) of these components to preserve focus, state, and any ongoing animations.

P.S. For more detailed information about differences between the two, see danabramov on Zero-Bundle-Size React Server Components

5. Advantages of Server Components

1. Conducive to Reducing Bundle Size

Because Server Components only run on server, components themselves and their dependencies are not bundled into client bundle, so can largely reduce package size (Facebook's pilot case reduced by about 30%)

On the other hand, multiple layers of abstraction encapsulation in between are all digested on server, reducing client burden

2. Can Access Backend Resources at Any Position in Component Tree

Can access backend resources at any position in component tree, this is also impossible in traditional SSR, because traditional SSR lacks client framework cooperation, can only require data to be fetched at once, then perform one synchronous component rendering, finally give result to client

Actually, original intention is to make relationship between components and their data sources clearer, code maintainability better:

// 类似这样的清晰依赖,每个组件明确知道其数据从哪来
function ArtistDetails({ artistId, children }) {
  const artistData = fetchDetails(artistId);
  // ...
}
function TopTracks({ artistId }) {
  const topTracks = fetchTopTracks(artistId);
  // ...
}
function Discography({ artistId }) {
  const discography = fetchDiscography(artistId);
  // ...
}

Modify components while modifying corresponding data together, when taking down components take down data requests together

3. Can Download Code On Demand

Because server has data, exactly knows which components need to be sent down:

Server Components let you only download the code that you actually need, they enable automatic code splitting for client code with support of a bundler plugin, you can use as much Server Components or as little as you like.

Simultaneously makes automatic code splitting possible, all Client Components import automatically load on demand, no longer need to use dynamic imports one by one, developers don't need to explicitly care, Server Components supports by default

4. Complementary with SSR

From SSR perspective alone, Server Components is component framework solving problems that SSR application framework cannot solve from component system level, such as:

  • Accelerating hydrate second rendering (reducing code amount client needs to load and execute, saving through indirect route)

  • Streaming rendering support (different from SSR streaming output, streaming rendering definitely needs component framework itself cooperating with server)

  • Allows partial refresh, preserving interaction state (traditional SSR can only be used for first screen)

These performance points cannot be maximized by SSR framework alone, and Server Components greatly accelerates this process

On the other hand, mentioned at beginning Next.js conducted in-depth exploration in hybrid rendering, allowing SSG, SSR, CSR to be mixed in multiple ways, seizing every opportunity for pre-rendering, its purpose is to improve first screen performance (including first screen performance in SPA route navigation and other interaction scenarios). Therefore, from certain significance, Server Components and these pre-rendering explorations reach same goal through different paths, therefore not conflicting and can be used cooperatively

6. Development Status

Although piloted inside Facebook as early as last year, it was just preliminary verification, still certain distance from production

And because moving components to server to run involves build, server-side rendering, route control and many other links, exceeding component framework scope, so React team plans to co-build with Next.js team cooperation, first try integrating with Next.js (of course, Server Components not limited to use with certain specific SSR framework, just try integrating one first)

Currently can play through official Demo, need to note, because Demo depends on postgre database, suggest starting through docker:

docker-compose up -d
docker-compose exec notes-app npm run seed

Through playing can understand some details, such as Server Components rendered intermediate format looks roughly like this:

[caption id="attachment_2344" align="alignnone" width="625"]server-components server-components[/caption]

Among them, Client Components returned in form of bundle index, native components (div, span, etc.) returned in JSON format, for example:

[
  "$",
  "header",
  null,
  {
    "className": "sidebar-note-header",
    "children": [
      ["$", "strong", null, { "children": "todo" }],
      ["$", "small", null, { "children": "5/4/21" }]
    ]
  }
]

P.S. For more technical details about React Server Components, see RFC: React Server Components

Reference Materials

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment