Skip to content

Client vs. Server Rendering

Video Summary

Note: This is a particularly visual lesson. It will be difficult to summarize in text. If you typically read the summaries instead of watching the videos, you may wish to make an exception for this one!

Earlier in the course, we built a Toast Playground project, using Parcel.

Let's dig into what happens when a user visits this page.

In the “Performance” tab, I'm going to throttle my network to "Fast 3G", and profile the page load. I get the following result:

screenshot of the “Performance” tab, showing a recorded profile

The very first thing that happens is we request the HTML file. This takes 698ms:

close-up screenshot showing that the HTML file took 698 milliseconds to download

If we right-click and “View Source”, we see that the initial HTML is quite spartan:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.png?v=1" />
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
<title>Toast Playground</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossorigin
/>
<link
href="https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap"
rel="stylesheet"
/>
<script defer src="/static/js/bundle.js"></script></head>
<body>
<div id="root"></div>
</body>
</html>

There's some metadata, and a couple of resources (a stylesheet from Google Fonts, a JS bundle), and not much else.

After 698ms, the HTML has been downloaded, and it immediately starts downloading the necessary resources. The biggest one, by far, is that bundle.js script:

close-up screenshot showing that the JS bundle took 2.68 seconds to download

For those 2.68 seconds, the user gets to stare at a blank white screen. We can't display anything to the user until the JS bundle has been downloaded.

The JS bundle takes a while to download because it contains all of the JavaScript we need, including:

  • The react and react-dom library code
  • Any NPM packages they depend on
  • Any other NPM dependencies we've installed (eg. react-feather)
  • All of the React components we've created (eg. Toast, ToastShelf, ToastProvider, etc).

Furthermore, if we disable JavaScript altogether, then the user will always see a blank white screen. No matter how long they wait, it will never resolve into our “Toast Playground” application.

Now, in reality, very few people disable JavaScript. Most websites nowadays require it. But every single user has the “JavaScript Disabled” experience for the first few moments, while the JS is being downloaded.

If someone is accessing your website on their phone in a car, and the car goes into a tunnel while the JS bundle is being downloaded, they get the “JavaScript Disabled” experience. And, for our Toast Playground project, that experience is a white screen of death.

Screenshot of the toast playground, but it's a blank white screen. The devtools show an “Elements” pane which has an empty div with the ID root.

By contrast, let's consider what happens on this course platform. If we disable JavaScript and refresh the page, it looks like this:

Screenshot of this course platform, where things mostly look alright

That looks pretty-much normal!

It's not perfect: certain things don't work, like the buttons in the code editor, the light/dark toggle, etc. But all of the content is visible. We can even click on the links in the lesson navigation, to move between lessons!

How is this possible? As we learned much earlier in the course (opens in new tab), React is typically used like this:

import React from 'react';
import { createRoot } from 'react-dom/client';
const element = React.createElement(
'p',
{ id: 'hello' },
'Hello World!'
);
const root = createRoot(document.querySelector('#root'));
root.render(element);

We point React at the empty <div id="root"> in our HTML, and it dynamically creates all of the DOM nodes we need!

Given that my course platform is also a React application, how is it working without JavaScript enabled in the browser?

We can get a bit of a hint if we right-click and "View Page Source". Instead of a barebones HTML template, we see a fully-formed (albeit not super readable) HTML file. All of the content is present in this file!

This is done using a technique called Server Side Rendering (SSR). The big idea with SSR is that we perform the first React render on the server.

When the user visits our website, a request is fired off to a server somewhere. Before, with Parcel, the server was a simple static file server. It provides the index.html file we created. But now, with SSR, a new HTML file is created.

Let's look at some code. This is an oversimplified example. We're keeping things high-level and abstract.

/* /src/server/index.js */
import renderToString from 'react-dom/server';
import App from './components/App';
export function handleRequest(request, response) {
const appContent = renderToString(<App />);
response.send(`
<html>
<body>
${appContent}
</body>
</html>
`);
}

We have a Node.js server that listens for requests. When a request is received, it invokes this function.

The renderToString method is similar to the root.render() method we use on the client: it creates a web of React elements, recursively rendering our components all the way down.

Instead of dynamically creating DOM nodes with document.createElement, however, it prepares an HTML string. We then interpolate that string into a complete HTML file, and send that to the client.

I want to be clear that this is not exactly how it works. renderToString is a real method, but it's been replaced by newer, fancier methods (we'll discuss them shortly). Plus, a real implementation would also need to include a <script> tag, to fetch the JS bundle.

Caveats aside, this is the whole idea with Server Side Rendering. We do the first render on a server somewhere, so that the user receives a “filled-in” HTML file.

What's the point of this? We can see the benefit by recording a performance profile of the course platform:

screenshot of the profiler running on this course platform

This course platform requires a lot of JavaScript. On a "Fast 3G" connection, it takes 6+ seconds to download it all!

And yet, the user can start reading the content in less than 1.5 seconds, because the HTML comes fully-formed. We don't have to wait for all of that JS to download!

For content-heavy websites and apps like this course platform, SSR can have a huge impact on user experience. The content appears 3-4x faster!

Server Side Rendering is not new: it's been an option in React for many years now. But it has changed a lot recently, and honestly, very few developers truly understand how this all works.

In this module, we're going to dig deeper into these ideas. I can't wait to show you how this all works!

Correction: In this video, I mention that the Toast Playground was created using Create React App. Since filming this video, that project has been migrated to use Parcel. Fortunately, both Create React App and Parcel use client-side routing, and so the result is the same.

Graphing it out

Throughout this module, we'll be looking at lots of different rendering strategies, and I find it's useful to plot these strategies out on a graph, to understand the timeline of what happens when, and what the potential performance implications are.

For example, here's a graph that shows the Server Side Rendering flow we've been talking about:

This is a data visualization which shows a sequence of events between client and server. Each event is represented here as a list item.

  1. "Render application" on server. Duration: 8 units of time.
  2. Request to server. Duration: 3 units of time.
  3. "Load JavaScript" on client. Duration: 16 units of time.

First, the server does the initial React render. Then, the HTML file is sent to the client (that's what the swoopy arrow indicates). Finally, the JavaScript bundle is downloaded and executed, adding interactivity to our application.

I've also added a couple of flags, to indicate what the user experiences during this timeline:

  • Content Paint — This is the moment that the user sees the main content on the page. For example, on my course platform, this is the moment they're able to start reading the lesson text. This is often referred to as Largest Contentful Paint (opens in new tab) (LCP).
  • Page Interactive — This is when the page becomes fully dynamic; on my course platform, this is the moment when users can interact with code sandboxes, toggle the dark/light color theme, etc. This is often referred to as Time To Interactive (opens in new tab) (TTI)

Here's another graph, showing the traditional “client-side rendering” we've been working with so far in this course:

This is a data visualization which shows a sequence of events between client and server. Each event is represented here as a list item.

  1. Request to server. Duration: 3 units of time.
  2. "Load JavaScript" on client. Duration: 12 units of time.
  3. "Render application" on client. Duration: 12 units of time.

The swoopy arrow at the start represents the server sending along an empty HTML file. Once that file has been received by the client, it can start downloading the JS bundle. Once the JS bundle is ready, the React application can finally be rendered.

Please note: these graphs are abstract approximations. They're meant to help you understand the sequence of events, not to be a 100% accurate reflection of reality. In practice, each of these steps will take a wildly different amount of time depending on unique circumstances (the amount of content, the speed of the server, the strength of the network, etc).

They're also oversimplified at the moment; In the next lesson, we'll learn about hydration, a puzzle piece currently missing from these graphs.