Yuko
Posted on April 26, 2024
This is a brief takeaway of what I learned in this YouTube video titled “Data Fetching with Server Components.” I also tried Next.js to see what features we already have.
Motivation
Issue
The React team wants to create apps with good user experience, cheap maintenance, and fast performance co-existing.
In general, having two of them in one project is comparatively more straightforward, but having all of them is pretty challenging.
Example: Good user experience and fast performance
With the code snippet, the app can render all components simultaneously and doesn’t suffer from the request waterfall, as it gets all data in one request. However, this code sacrifices the cheap maintenance because each component's API responses are coupled.
function ArtistPage({artistId}){
const stuff = fetchAllTheStuffJustInCase()
return (
<ArtistDetails artistId={artistId} details={stuff.details} />
<TopTracks artistId={artistId} details={stuff.topTracks} />
<Discography artistId={artistId} details={stuff.discography} />
</ArtistDetails>
)
}
// https://youtu.be/TQQPAU21ZUw&t=4m12s
🗒️ This code reminds me of the Easier to Change (ETC) principle provided in *The Pragmatic Programmer*. According to the book, “Good design is easier to change than bad design.” It also says, “Decoupled code is easier to change.”
The code below can reduce the maintenance cost but causes a so-called request waterfall, sacrificing the performance speed. In addition, the app no longer renders all components simultaneously, and now they all depend on each request order.
// Ideal way
function ArtistPage({artistId}){
return (
<ArtistDetails artistId={artistId} />
<TopTracks artistId={artistId} />
<Discography artistId={artistId} />
</ArtistDetails>
)
}
function ArtistDetails({artistId}) {
const details = fetchDetails(artistId)
// ...
}
function TopTracks({artistId}) {
const topTracks = fetchTocpTracks(artistId)
// ...
}
function Discography({artistId}) {
const discography = fetchDiscograhy(artistId)
// ...
}
// https://youtu.be/TQQPAU21ZUw&t=5m20s
🗒 I like this part because I can sense the strong eagerness to break through what we call impossible.
React Team’s Solution: Put data fetching logic on the server
Server Side Rendering vs React Server Components
Server Side Rendering (SSR)
SSR is a technique that serves an initial HTML when the first page loads. Once the first HTML is loaded, it is required to download/parse/execute Client Components.
React Server Components
React Server Components are not interactive with the client side. Client Components always take responsibility for interacting with users and trigger re-fetch Server Components trees corresponding to the user’s actions. Notably, the client-side state is preserved when the re-fetch. We can do this with Server Components because Server Components don’t render to HTML, but they render into a special format.
Here is an example of where the client states are preserved when the re-fetch with Next.js. The search input appears only when the search input state is true, and the to-do list is filtered by the filter query controlled on the server side.
cf) Here is how Next.js explains the interaction between Server Components and Client Components. Next.js calls the special format React Server Component Payload.
React Foundations: Server and Client Components | Next.js
Server Component Features
Zero Effect on the Bundle Size
We can reduce dependencies downloaded to the client by moving some heavy dependencies components to the server.
⚠️ Not exactly HTML files (as Server Components render into a special format), but I mention only HTML here for the sake of ease.
Direct Access to the Backend Resources
Because Server Components are rendered on the server side, they can access the server-side resources directly.
// Example 1
import { resolve, join } from 'path';
import { readdir, readFile } from 'react-fs';
import marked from 'marked';
const folder = resolve(__dirname + '/../posts');
function Blog() {
return readdir(folder).map(name =>
<article key={name}>
{/* Access the file system directly without creating any API */}
{marked(readFile(join(folder, file), 'utf8'))}
</articke>
);
}
// https://youtu.be/TQQPAU21ZUw&t=37m57s
// Example 2
import marked from 'marked';
import { db } from './db';
function Blog() {
return db.posts.getAll().map(post =>
<article key={post.id}>
{marked(post.html)}
</article>
)
}
// https://youtu.be/TQQPAU21ZUw&t=38m05s
Automatic Code Splitting
Server components allow for the loading of only the required code. This means that server components are not downloaded to the client, and even client components that are not being used do not need to be downloaded.
// Example
import EditToobar from './EditToolbar.client';
function Comment({ comment, currentUser }) {
const canEdit = (
currentUser.isAdmin ||
currentUser.id === comment.author.id
);
return (
<Box>
{/* EditToonar is downloaded only when canEdit === true */}
{canEdit && <EditToolbar />}
<p>{comment.text}</p>
</Box>
)
}
// https://youtu.be/TQQPAU21ZUw&t=41m25s
This is questionable with Next.js, as I found client components not rendered in the initial render when I inspected the source after running npm run build.
Server Components require users’ decision
Server Components let you decide the Client and Server Components tradeoff for every concrete use case.
Rendering: Composition Patterns | Next.js
🗒️ I think this is a double-edged sword feature as we have to understand the ins and outs of React, including both Server Components and Client Components (and Shared Components), to decide which to use in each case. In other words, it all depends on developers to make the most of the server components or to make them waste treasures.
Server Components provide modern UX with a server-driven mental model
Server Components enable developers to create modern and app-like user interfaces in an old-school way.
// Example
import SearchInput from './SearchInput.client';
function SearchResults({searchText}) {
const results = fetch('/search?q' + searchText).json();
return (
<>
{/*This component triggers re-fetch of the server tree*/}
<SearchInput />
<ul>
{results.map(result =>
<li key={result.id}>{result.text}</li>
)}
</ul>
</>
)
}
// https://youtu.be/TQQPAU21ZUw&t=44m45s
Ref
Introducing Zero-Bundle-Size React Server Components - React
rfcs/text/0188-server-components.md at main · reactjs/rfcs
The whole demo code is available here.
original article
Posted on April 26, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 27, 2024
November 7, 2024