I Tested React Server Components And I'm Not A Fan (Yet).
I've tested React Server Components (RSC). I find them very fun, but also not very useful. This article explains why.
If you want more technical details, check the link section at the end of this article.
What Are React Server Components?
Sever Components are the solution designed by the React Core Team to heavy JS bundles. Server components render on the server, so there is no need to push their JS code to the client - only the rendered markup, using a specific format. Server Components actually minimize the front-end react bundle size.
Is it like SSR ???: This question is repeated in all articles about RSC, so I couldn't ignore it. No, Server Components aren't not exactly like SSR. But they can be used in addition to a Server Side Rendering system:
- Your page is first built and served in HTML by your SSR.
- Your main bundle js is served too to allow navigation and interactions.
- And some components in your pages are called by the react "client", pre-rendered by the server, and included on the fly
There is a (long but interesting) presentation video on the core team presentation page
With RSC, components can be split into Server (in blue color) or Client (in beige color)
Tip: One important thing to keep in mind is that it's an early feature, whose design and creation are still on the table. In this situation, any conclusion will have to be revised later depending on the design advancement.
Why Are They So Attractive?
One word: Simplicity.
Server Components seem to be the missing piece towards the ultimate web development Holy Grail: the possibility to use the same code for the back-end and the front-end, without any performance penalty. It's our Grand Unified Theory, especially in the JS world, and React Server Components is bringing its stone.
Server Components used on the server-side simplify access to the data or the backend system in general. In particular, they reduce the numer of application states to consider.
Knowing that, who can remain indifferent?
How To Test React Server components
The core team presentation page gives a link to a fine demo repository on github. It's a simple notes management application using Postgresql for the data.
There are also various forks, and I preferred the pomber/server-components-demo one because it doesn't use Postgresql for the database. So it's just one less tool to install and configure. It uses a simple data file instead.
Tip: This toy project uses some of the lastest Node.js features, so you need to install Node 15. On previous Node versions, you'll see this error: node: bad option: --conditions=react-server
Some Basic Principles
When using this RSC version of React, you can have 3 different file extensions:
MyComponent.client.tsx
— This is a client componentMyComponent.server.tsx
— This is a server componentMyComponent.tsx
— This is a shared component, it can be used on both sides
Note: I used .tsx
because I wrote my components using TypeScript, but you can also use pure JavaScript with the .js
suffix instead.
A Few Rules To Apply
For all this to work, there are just a few things to have in mind.
A server component can not have a state. You won't be able to use useState
or useEffect
. It also can not deal with events. Forget onClick
, onChange
, and so on.
Their strength is that as they will be rendered server-side before being sent to the browser, so you have access to server features for fetching data by example.
For interactions or states, use a client component. Only this type of component will be able to interact with the browser. And at the opposite, it has no direct contact with the server environment, so no possibility to directly fetch data in this component.
Then, logically, the shared components are the more restricted. You'll only use them for a basic and repetitive tasks that can happen on both sides, like some simple <Containers>
.
In Practice
To code your application efficiently, you'll split your files using the server/client structure.
Each component that needs to acces to the data will be a .server.tsx
Each component that needs interact with the user will be a .client.tsx
If one component has to do both, you should split it in two files and compose them as needed.
<SharedContainer>
<ClientActionComponent onClick={open()}>
<ServerTitleComponent />
</ClientActionComponent>
<ServerListComponent>
<ClientActionComponent onClick={addItem()}>
</ServerListComponent>
</SharedContainer>
Why Are React Server Components Disappointing?
Fetching data directly in a server component is so awesome. But strangely, the demo repository is, in my opinion, a great example of bad use of this technology. Let me explain.
Each Note is built by a server component Note.server.js
to make the data fetching easier. So when you click to display a note, the server returns a pre-rendered component in an internal format that is not HTML. Here is an example of a rendered Note returned by the server ; it weighs 4.2kB:
M1:{"id":"./src/SearchField.client.js","chunks":["client5"],"name":""}
M2:{"id":"./src/EditButton.client.js","chunks":["client1"],"name":""}
S3:"react.suspense"
M4:{"id":"./src/SidebarNote.client.js","chunks":["client6"],"name":""}
J0:["$","div",null,{"className":"main","children":[["$","section",null,{"className":"col sidebar","children":[["$","section",null,{"className":"sidebar-header","children":[["$","img",null,{"className":"logo","src":"logo.svg","width":"22px","height":"20px","alt":"","role":"presentation"}],["$","strong",null,{"children":"React Notes"}]]}],["$","section",null,{"className":"sidebar-menu","role":"menubar","children":[["$","@1",null,{}],["$","@2",null,{"noteId":null,"children":"New"}]]}],["$","nav",null,{"children":["$","$3",null,{"fallback":["$","div",null,{"children":["$","ul",null,{"className":"notes-list skeleton-container","children":[["$","li",null,{"className":"v-stack","children":["$","div",null,{"className":"sidebar-note-list-item skeleton","style":{"height":"5em"}}]}],["$","li",null,{"className":"v-stack","children":["$","div",null,{"className":"sidebar-note-list-item skeleton","style":{"height":"5em"}}]}],["$","li",null,{"className":"v-stack","children":["$","div",null,{"className":"sidebar-note-list-item skeleton","style":{"height":"5em"}}]}]]}]}],"children":["$","ul",null,{"className":"notes-list","children":[["$","li","0",{"children":["$","@4",null,{"id":0,"title":"Meeting Notes","expandedChildren":["$","p",null,{"className":"sidebar-note-excerpt","children":"This is an example note. It contains Markdown!"}],"children":["$","header",null,{"className":"sidebar-note-header","children":[["$","strong",null,{"children":"Meeting Notes"}],["$","small",null,{"children":"10:11 AM"}]]}]}]}],["$","li","1",{"children":["$","@4",null,{"id":1,"title":"Make a thing","expandedChildren":["$","p",null,{"className":"sidebar-note-excerpt","children":"It's very easy to make some words bold and other words italic with Markdown. You can even link to React's..."}],"children":["$","header",null,{"className":"sidebar-note-header","children":[["$","strong",null,{"children":"Make a thing"}],["$","small",null,{"children":"10:11 AM"}]]}]}]}],["$","li","2",{"children":["$","@4",null,{"id":2,"title":"A note with a very long title because sometimes you need more words","expandedChildren":["$","p",null,{"className":"sidebar-note-excerpt","children":"You can write all kinds of amazing notes in this app! These note live on the server in the notes..."}],"children":["$","header",null,{"className":"sidebar-note-header","children":[["$","strong",null,{"children":"A note with a very long title because sometimes you need more words"}],["$","small",null,{"children":"10:11 AM"}]]}]}]}],["$","li","3",{"children":["$","@4",null,{"id":3,"title":"I wrote this note today","expandedChildren":["$","p",null,{"className":"sidebar-note-excerpt","children":"It was an excellent note."}],"children":["$","header",null,{"className":"sidebar-note-header","children":[["$","strong",null,{"children":"I wrote this note today"}],["$","small",null,{"children":"10:11 AM"}]]}]}]}]]}]}]}]]}],["$","section","1",{"className":"col note-viewer","children":["$","$3",null,{"fallback":["$","div",null,{"className":"note skeleton-container","role":"progressbar","aria-busy":"true","children":[["$","div",null,{"className":"note-header","children":[["$","div",null,{"className":"note-title skeleton","style":{"height":"3rem","width":"65%","marginInline":"12px 1em"}}],["$","div",null,{"className":"skeleton skeleton--button","style":{"width":"8em","height":"2.5em"}}]]}],["$","div",null,{"className":"note-preview","children":[["$","div",null,{"className":"skeleton v-stack","style":{"height":"1.5em"}}],["$","div",null,{"className":"skeleton v-stack","style":{"height":"1.5em"}}],["$","div",null,{"className":"skeleton v-stack","style":{"height":"1.5em"}}],["$","div",null,{"className":"skeleton v-stack","style":{"height":"1.5em"}}],["$","div",null,{"className":"skeleton v-stack","style":{"height":"1.5em"}}]]}]]}],"children":"@5"}]}]]}]
M6:{"id":"./src/TestButton.client.js","chunks":["client7"],"name":""}
J5:["$","div",null,{"className":"note","children":[["$","div",null,{"className":"note-header","children":[["$","h1",null,{"className":"note-title","children":"Make a thing"}],["$","div",null,{"className":"note-menu","role":"menubar","children":[["$","small",null,{"className":"note-updated-at","role":"status","children":["Last updated on ","22 Apr 2021 at 10:11 AM"]}],["$","@2",null,{"noteId":1,"children":"Edit"}]]}]]}],["$","div",null,{"className":"note-preview","children":["$","div",null,{"className":"text-with-markdown","dangerouslySetInnerHTML":{"__html":"<p>It's very easy to make some words <strong>bold</strong> and other words <em>italic</em> with Markdown. You can even <a href=\"https://www.reactjs.org\">link to React's website!</a>.</p>\n"}}]}],["$","@6",null,{}]]}]
Doing this the "usual way" would have just returned few octets. In JSON for instance (218 Bytes, so 20 times less):
{
"id": "",
"updated": "22 Apr 2021 at 10:11 AM",
"title": "Make a thing",
"text": "It's very easy to make some words bold and other words italic with Markdown. You can even link to React's website!."
}
To reduce the JS bundle size, Server Components increase the AJAX network exchanges. And even though a user action may only result in a small data update, the server will return a large chunk of updated markup.
The demo has a solution to avoid it, for a part. It's caching. There is a full cache system implemented to avoid calling the same pre-rendered component multiple times. It's a partial solution (all Note components have to be transferred and stored at least once for it to work), and it's a costly solution because this cache system is not included in RSC today. It's your job as a developer to implement it if you want. The added complexity isn't worth the benefit in my opinion.
The impact on performance may be mitigated by network protocols. Modern wireless networks use a default packet size of 1,500 Bytes, so in many cases, the difference between returning JSON data or compressed component markup may not be significant for the end user.
Nevertheless, I'm convinced that using RFC for listing items, whatever they are, is not a good solution. JSON APIs are here to reduce and simplify exchanges between the client and the server, especially when there are many calls. By coding an item list with RFC, you'll reduce the initial bundle, and then enlarge every network exchange, and possibly, enlarge the memory of your browser to keep a complex cache system alive.
My Conclusion (For The Moment)
There are really good points:
- Possibility to use the same code for the server part: easy to maintain
- Can simplify the back-end code with direct access to the data.
- Having smaller base bundles is a good thing
But there are some lacks, too:
- Much bigger network exchanges for pre-rendered components than for simple API calls
- And/Or need of a complex cache system to limit these exchanges
I think this feature can be useful for certain usages and negative for others. I's not a clear win in all situations. It's the developer's role to be careful of what they do with the news toys they get.
Splitting a website to load only the main page in the bundle, and having the other complex pages called and pre-rendered from the server can be a good tradeoff. Building a list of numerous items with server rendering doesn't look so great. And the question should have to be asked for each possible usage I think.
Finally, React Server Components are just another way to place the slider between server-side and client-side tasks.
The more work you ask to your server, the more data you'll have to transfer to the client
The less work you ask to your server, the more code you'll have to transfer to the client
I think it can be useful, but not a real revolution in the Web.
Let's continue to follow this subject to see where it'll bring us.
Some great links
The core team presentation page
The article that got me into it because of its unexpected simplicity