React Server Components

đź“– tl;dr: Notes dump for RSC


Miscellaneous notes

  • From Reactathon interview of Ben, Ryan and Tanner, ideally will run all the logic on the server and ideally eliminate the need to have hydration on Client side for server rendered apps.
  • It eliminates the need to have the server side logic on your client side code.
  • If something needs to be run on the client then used a directive to run all of it as a Client bundle.
  • From Jeff’s talk, code complexity increases but so do the performance benefits with React.
  • Some frameworks can reduce complexity.
  • Parallel non blocking data loading by default.

Notes from the RFC Read

  • Server Components allow developers to build apps that span the server and client.
  • Combines the rich interactivity of client-side apps with the improved performance of traditional server rendering.
  • Server Components run only on the server. Hence they have zero constributions to bundle size.
  • They can access Server side data resources like Databases, micro services directly.
  • Server Components load data on the server and then pass it on to Client Components via props on the network.
  • Server Components preserve client state when reloaded. This means that client state, focus, and even ongoing animations aren’t disrupted or reset when a Server Component tree is refetched.
  • Server Components can incrementally stream rendered bits and pieces of the UI over network - Incremental Hydration.
  • Combined with Suspense we are able to develop custom loading states on the Client side for Server Data.
  • Developers can also share code between the server and client.
  • We use use client directive to indicate a client side component.
    // Note.js - Server Component
    import db from 'db'; 
    // (A1) We import from NoteEditor.js - a Client Component.
    import NoteEditor from 'NoteEditor';
    
    async function Note(props) {
    const {id, isEditing} = props;
    // (B) Can directly access server data sources during render, e.g. databases
    const note = await db.posts.get(id);
    
    return (
        <div>
        <h1>{note.title}</h1>
        <section>{note.body}</section>
        {/* (A2) Dynamically render the editor only if necessary */}
        {isEditing 
            ? <NoteEditor note={note} />
            : null
        }
        </div>
    );
    }
  • Supports top-level awaits to ensure access to DB or micro services.
  • Does not support concepts of state or effects as these are not relevant in a Server space.
  • Can conditionally render client components, servers hand over the responsibility to client. These components are treated as normal React components and bundled accordingly.
  • Client Components are just regular components.

  • The fundamental challenge was that React apps were client-centric and weren’t taking sufficient advantage of the server.
  • 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.

  • Using libraries is helpful as developers but increases bundle size and can hurt application performance.
    // NOTE: *before* Server Components
      import marked from 'marked'; // 35.9K (11.2K gzipped)
      import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped)
    
      function NoteWithMarkdown({text}) {
      const html = sanitizeHtml(marked(text));
      return (/* render */);
      }
  • With Server components we can render this on the server and do not need to send this as a bundle to the client:
    // Server Component === zero bundle size
      import marked from 'marked'; // zero bundle size
      import sanitizeHtml from 'sanitize-html'; // zero bundle size
    
      function NoteWithMarkdown({text}) {
      // same as before
      }
  • A common theme is that developers have to expose additional endpoints to power their UI, or use existing endpoints that weren’t always designed with that UI in mind.
  • Server Components treat all imports of Client Components as potential code-split points.
  • The problem isn’t really the round trips, it’s that they are from client to server. By moving this logic to the server we reduce the request latency and improve performance.
  • If a Server Component with multiple layers of wrappers for configurability ends up rendering to a single element, then that’s all that will be sent to the client.
  • PHP, Rails, etc — is certainly one way to address some of these challenges, but that approach makes it much harder to build rich, interactive experiences. This reflects a long-standing tension in the world of application development that predates the web: whether to use thin or thick clients.
  • But mixing server and client rendering often means mixing technologies: writing code in two languages, using two frameworks, keeping two sets of idioms and ecosystems in mind.

The flow of Rendering:

  1. Router will match the requested URL to a Server Component, passing the route parameters as props to the component.
  2. React will render the root Server Component and any children that are also Server Components. Rendering stops at native components (divs, spans etc) and at Client Components. Native components are streamed as a JSON description of the UI, and Client Components are streamed including the serialized props.
  3. You can think of the data streamed to the target as JSON but with slots for components that suspend, where the values to place into those slots are provided as additional items later in the response stream.
  4. On the client, the framework receives the streamed React response and renders it on the page with React.
  5. React deserializes the response and renders the native elements and Client Components. This happens progressively — React doesn’t need to wait for the whole stream to finish in order to show something.
  6. React reconciles (merges) the new rendered output with the existing components on screen. Because the description of the UI is data, not HTML, React can merge new props into existing components, preserving important UI state such as focus or typing input, or triggering CSS transitions on existing content. This is a key reason that Server Components return rendered UI output as data (“virtual DOM”) instead of HTML.
  7. Client Components can’t access server-only features like the filesystem, Server Components can’t access client-only features like state, and Client Components may only import other Client Components.

Server Components Rules:

  • ❌ May not use state, because they execute (conceptually) only once per request, on the server. So useState() and useReducer() are not supported.
  • ❌ May not use rendering lifecycle (effects). So useEffect() and useLayoutEffect() are not supported.
  • ❌ May not use browser-only APIs such as the DOM (unless you polyfill them on the server).
  • ❌ May not use custom hooks that depend on state or effects, or utility functions that depend on browser-only APIs.
  • âś… May use async / await with server-only data sources such as databases, internal (micro)services, filesystems, etc.
  • âś… May render other Server Components, native elements (div, span, etc), or Client Components.

Client Components Rules:

  • ❌ May not import Server Components or call server hooks/utilities, because those only work on the server. However, a Server Component may pass another Server Component as a child to a Client Component: <ClientTabBar><ServerTabContent /></ClientTabBar>. From the Client Component’s perspective, its child will be an already rendered tree, such as the ServerTabContent output. This means that Server and Client components can be nested and interleaved in the tree at any level. ❌ May not use server-only data sources. âś… May use state. âś… May use effects. âś… May use browser-only APIs. âś… May use custom hooks and utilities that use state, effects or browser-only APIs.

  • To mark a component as a Client Component, add “use client’` directive at the top of your file.
  • To be used only for Client Components that you import from Server Components.
  • Areas of research and open questions

Notes from implemeneting RSC from scatch by Dan

Discussion here *

Notes from RSC with Ben and Dan here

  • Let us keep the UX as good as modern Apps but let us also keep the mental model of a traditional request response based structure.
  • Like jsx Server components are slightly complex and not too conventional to understand.
  • If you think from a Streaming perspective the model of splitting components to client and Servers makes more sense to avoid Network waterfalls.
  • Rails to Client-side.
  • Server and Client meanings in RSC’s context is very important. A stuff that transfers and a stuff that translates.
  • Client stuff also runs on the Server during SSR so it gets tricky to have a clear frame of mind while approaching this.
  • It is closer to a Cient-Server Model rather than a convetional Server or Client.
  • One way to think about is Build time Components.
  • Is DIY Server Components needed or is NextJS is the way to go?
  • With Client-Only you are signing up for a lock-in wherein the contents are CSR only and if later you want pure HTML then it gets tricky. We should not need a bundle for this.
  • so best way will be start with Client only and then move to Server Components when something closer to pure HTML is needed.
  • Router belongs outside the React app.
  • Recently say getServerSideProps can be replaced with async/await at top level.
  • Aim from a framework perspective is to get the frameworks folded in to more vanilla React.
  • For Bundling of Server Components it relies on features that do not exist on Bundlers today.
  • Next gen Bundlers and Routers.
  • For present bundlers what needs support is the use client API.
  • Code splitting on Client vs Server level.
  • An ideal bundler will take the above idea and split put code for server and client trees. As a single task with multiple bundle outputs for different targets.
  • Bundlers need to be redesigned around this idea.
  • Modern bundlers will need to have features like Build caching, compiling to the last method that is needed.
  • Sometimes server sent props can be quite heavy and become a performance bottle neck
  • From a Client it can get really tricky to understand what is happening on the server.
  • Server Components do not have a waterfall of network calls.

Notes from RSC with Kent, Joe and Dan here

  • React’s take for what a back-end server code might look like
  • Server in this scenario means ahead-of-time.
  • Little more colocation.
  • Server has lower latency when accessing data.
  • RSC is stil React. Sharing of code becomes easier.
  • Architecture becomes easier
  • For an MPA when we navigate, we are kind of blowing away the state. With RSC say opening up of navigation states etc are retained.
  • Reduced amount of Client Side components.
  • Say a Tweet component that does everything, fetches everything from the API and we just need to render it.
  • Ability to compose server components all the way down.
  • a Waterfall if it does happen, happens on the Server.
  • RSC does not belong on the Edge necessarily.
  • Server component layer should be closest to the data layer.
  • SSR and Server Component layers are 2 different layers
  • SSR can be placed on the Edge and RSC closer to the Data layer.
  • Very hard to actually write JS code that can avoid waterfall. We need to endure to use Promise.all say or use await only where needed.
  • Route segments can be fetched in parallel.