iTranslated by AI
Explaining the Isomorphism of React Server Components
Since the reference implementation of Next.js + React Server Components has been released, here are my notes on understanding it while running it locally.
My motivation for writing this is that, looking at Twitter, there were reactions to React Server Components like "It's just a regression to the server-side" or "Isn't it just doing dangerouslySetInnerHTML on SSR results?" I want to clear up those kinds of misunderstandings.
Introducing Zero-Bundle-Size React Server Components – React Blog
tl;dr
Rendering results from the server can be JSON-serialized and requested by the client. Since Node.js APIs can be used during the process of assembling React Elements on the server side, there's no need to implement an API every time; it can be encapsulated within an RPC. It's a type of Isomorphism (client-server isomorphism).
In other words, these are asynchronous components that execute and update on the server, and their counterparts would likely be asynchronous views like Rails Hotwire or Phoenix LiveView.
The Purpose of React Server Components
While there is essentially one means, there are two intertwined purposes, so let's organize them.
- Execute libraries that would make the bundle size heavy (e.g., marked, prettier, babel) on the server side and return only the results.
- Realize Isomorphism that naturally encapsulates API calls between Client and Server through RPC.
Conventional React Elements were structures that could not be JSON-serialized because they handle things like function references. However, React Server Components allow the client to request a serialized version of a React Element. In exchange, you cannot pass things like functions that cannot be JSON-serialized. If you want to handle event handlers, you render from a Server Component that includes a Client Component and then hydrate it on the client side.
Full-stack frameworks that extend Next.js, such as Blitz, have similar implementations.
Investigating how blitz-js enables calling server-side functions from the client
Difference from just receiving SSR'd HTML
While *.server.js is rendered on the server side, it can also include client-side components from *.client.js. The client receives these not as pure HTML, but as React Elements. Upon receipt, hydration takes place, injecting JS logic so they behave dynamically as React Components on the client.
Looking at the Server Component components/App.server.js reveals how this works.
App.server.js is mounting SearchField.client.
import React, { Suspense } from 'react'
import SearchField from './SearchField.client'
import Note from './Note.server'
import NoteList from './NoteList.server'
import AuthButton from './AuthButton.server'
import NoteSkeleton from './NoteSkeleton'
import NoteListSkeleton from './NoteListSkeleton'
export default function App({ selectedId, isEditing, searchText, login }) {
return (
<div className="container">
{/* Omitted */}
<section className="sidebar-menu" role="menubar">
<SearchField />
<AuthButton login={login} noteId={null}>
Add
</AuthButton>
</section>
<nav>
<Suspense fallback={<NoteListSkeleton />}>
<NoteList searchText={searchText} />
</Suspense>
</nav>
</section>
<section className="col note-viewer">
<Suspense fallback={<NoteSkeleton isEditing={isEditing} />}>
<Note login={login} selectedId={selectedId} isEditing={isEditing} />
</Suspense>
</section>
</div>
</div>
)
}
It sends JSON as input and returns a React Element that does not handle function handlers.
Realized isomorphism
For example, in Rails, when fetching app/views/index.erb, ActiveRecord is hit and the SQL execution results are included in the View. Similarly, the process of assembling React Server Component Elements takes place in Node.js, so you can fully utilize server-side capabilities.
A while ago, I wrote a sample using Prisma ORM with Next.js's getServerSideProps, and I think Server Components will have a similar feel when wrapped with hooks.
import type { GetServerSideProps } from "next";
import prisma from "../lib/prisma";
type Props = {
count: number;
};
export const getServerSideProps: GetServerSideProps<Props> = async (ctx) => {
const count = await prisma.user.count();
return {
props: {
count,
},
};
};
export default function Index(props: Props) {
return <div>user count: {props.count}</div>;
}
In the initial demo, it was implemented with the /react endpoint, and in next-server-component, it was implemented with /api. By implementing just one RPC, the need to implement read-only APIs disappears. Since resource references for reads are integrated into the assembly process of the Server Component, they don't need to be exposed to the client as an API.
That said, update mechanisms still need to be implemented separately. When an update occurs, you need to re-fetch the Server Component and perform a diff-update of the result using React.
Reading next-server-components/NoteEditor.client.js at master · vercel/next-server-components, you can see how it re-renders the preview by discarding the cache and re-fetching when the Markdown is updated.
This means it's an asynchronous View that updates the client using state on the server. As mentioned earlier, the points of comparison for this would be Rails Hotwire or its likely inspiration, Phoenix LiveView.
Phoenix.LiveView — Phoenix LiveView v0.15.3
While Hotwire and LiveView adopt this implementation by rejecting JS, React Server Components arrive at a similar implementation by actively utilizing JS to optimize and improve the workflow. Hotwire uses a wild implementation that pushes changes via WebSockets for all connections, which feels like it might have operational challenges. Personally, I feel React Server Components have more growth potential. After all, using the same language on both the client and server is a strong advantage.
How it might evolve in the future
Looking at the current code, the way webpack plugins are used is difficult, and server implementations need to reference a dedicated generated manifest, so it's not yet refined. Most of this will likely become boilerplate, so in the future, it should be possible to generate code that runs as an endpoint on a Node.js http-server without any configuration.
This is purely speculation, but I think the reason the demo came out for Next.js is a message that since Next.js is the most widely used server implementation for React SSR, they will develop in collaboration with Vercel (the creators of Next.js) rather than developing it within Facebook where PHP is mainstream. Furthermore, I suspect Facebook might want to switch to Server Components internally if possible. In the experimental repository, they prepared wrappers for Postgres like react-pg, while in the demo, they made it look like they were referencing external resources via isomorphic fetch to an external server. I take this as a message like, "Internally, we'll be hitting PHP endpoints."
However, currently, while it's implemented on Next.js, it's just using API Routes and isn't specifically integrated into Next.js itself. I think the most likely scenario for the future is that it will be usable without configuration in Next.js, and getServerSideProps will simply transition into Server Components.
Outside of Next.js, it will probably look something like deploying by mounting webpack build results in a Node.js container.
Discussion