Exploring “Data Fetching with React Server Components” with Next.js

lada496

Yuko

Posted on April 26, 2024

Exploring “Data Fetching with React Server Components” with Next.js

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
Enter fullscreen mode Exit fullscreen mode

🗒️ 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
Enter fullscreen mode Exit fullscreen mode

🗒 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

Before

After

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.

A very simplified SSR image

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.

Demo

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
Enter fullscreen mode Exit fullscreen mode
    // 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Demo of experimental code

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
Enter fullscreen mode Exit fullscreen mode

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

💖 💪 🙅 🚩
lada496
Yuko

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