React Server Side Rendering doesn’t really need to be that complicated…

I set out to prove that I could go straight from a Create React App site to SSR without adding a bunch of 3rd party libs. Makes it much easier to understand exactly what is going on.

Most of the other React SSR articles I see use Redux, but not every app uses Redux. I didn’t want to impose that design restriction on making SSR work.

I want to show that React SSR can work:

  • without Next.js

  • without Redux

The Plan

  • Start with a React project started from Create React App that has some API calls, defines title tag, and a couple routes

  • Show what problem we are trying to solve and how we would like to solve it

  • Evolve the app into a React Server Side Rendering, showing the challenges encountered along the way

The App

Title Tags

On the main page, I want it to have the title tag “Star Wars Heroes - List”

On the Detail page I want to have it include the name of the hero, like “Star Wars Heroes - Luke Skywalker”

API Calls

On the main list page, we call /heroes to get a list of all the heroes.

On the individual detail page, we call /heroes/0 to get a single hero.

As Yoda famously says…


The code is on Github at Zerrtech/zerrtech-react-ssr-demo

4 branches, 1 for each iteration

  1. step-1-normal-app

  2. step-2-ssr-initial

  3. step-3-ssr-server-seed

  4. step-4-ssr-api

Step 1: Initial App Demo

Branch: step-1-normal-app

To most easily see what the page fetched from the server contains, pop open your dev tools (I’m using Chrome) and click on the very first request. Check out the Preview tab which will render it as HTML. Ummm, yeah, it’s blank. It’s supposed to be.

This empty initial page load is by design.

It’s a Single Page App (SPA) after all.

React renders the app into the div id=”root”

What’s wrong with that?

Google can crawl Javascript on a page, but it takes extra steps and can delay full indexing since they have to wait for rendering by doing two passes.

Performance wise, one trip to the server is quicker than N trips to the server to generate a page, especially in mobile where the maximum number of parallel requests is smaller and connections are slower.

Combined with SSR caching, this can mean that each page is only rendered once and used by many users.  Loading indicators added while waiting for API calls wouldn’t really show up.

One negative with SSR is that it will take more time to get to the first usable content. After all, delivering a blank HTML page is pretty quick. Then you can use loading indicators to indicate progress. This is opposed to waiting until the entire page is rendered before showing anything.

Rendering a HTML-only page

Image from SEOpressor

Rendering a Javascript Single Page App

Image from SEOpressor

Google uses Chrome 41 to crawl your pages.

I have version 71

Take a look at the differences by looking at Can I Use - 41 vs. 71

Use the Google Search Console to Fetch as Google (or new Search Console URL Inspection). You need to be the owner of your domain to do this, can’t just check out someone else’s page.

Read up on this at the Google Search Rendering Guide.

Why don’t all sites do Server Side Rendering then?

  • It’s good for content-heavy, largely publicly accessible apps, where SEO is hugely important.

  • If most of your content is behind a login, then SSR can’t get at it either.

  • A proper SSR implementation would use a layer of caching, so if you have data that absolutely needs to be as up to date as possible, there will be some tuning to do with caching vs. up to date data access.

  • Requires redesigning data fetching within components, ideal solution is specific to your app, no generic solution

Wait!!! Weren’t all sites like this in 1999?

How does React rendering normally work?

  • A user types your home page URL into the browser

  • Browser sends a request to the server for your home page

  • HTML for home page is returned to Browser, but has a blank content body and links to scripts/CSS

  • React boots up in Browser, renders the home page

  • Browser React component may fire off API requests that when they return, causes another React render

How does React Server Side Rendering work?

  • A user types your home page URL into the browser

  • Browser sends a request to the server for your home page

  • Server fetches data needed for the home page, seeds your component state, renders your React components, gets the HTML created by them, stuffs it into your React root in index.html and returns it to the browser

  • React boots up in Browser, realizes everything was already rendered so has no changes (virtual DOM)

  • Browser React component does not need to fire off API calls because they were already done on the server

Step 2: Initial SSR

Branch: step-2-ssr-initial

Notice here that the difference is that the HTML page in the debugger shows “Loading…”, our loading indicator. Yeah, I know, not much change. Just stick with me, we’re building this thing step by step.

We aren’t doing any API calls on the server in this initial step. But we got some React rendered by the server, enough to render our loading indicator.

Initial SSR Code Changes

In this step I added the server script. It is:

  • NodeJS Express

  • Babel compiled

  • Uses React DOM Server renderToString and StaticRouter

You first build the React production build the usual way: yarn build

Then the SSR server is started using: yarn start:ssr

Code diff - step 1 vs. step 2

Static files are served out of the build directory

We load our Routes under StaticRouter instead of BrowserRouter that we would normally use in the React frontend.

We grab our index.html and stuff our HTML in

Step 2 - server.js

Initial SSR Review

  • Notice no API calls were done within SSR

  • The render is synchronous, makes one pass through then returns what it has

  • Fact: when doing SSR, componentDidMount is not called

  • We need to do our API calls on the server, then seed our state within our component so SSR can do the full page

Step 3: SSR Server Seed

Branch: step-3-ssr-server-seed

Now we can see in the preview window that all of our content is there on the initial page load, and the title tag is there too! Booyah!

SSR Server Seed Code Changes

  • Added fetching data into the server script by using a static method on each route component

  • Pass the data into the component using StaticRouter context param

  • Render Helmet and replace in HTML

Code diff - step 2 vs. step 3

SSR Server Seed Data Fetching

Static method called getInitialState() added into Route component.  Static so it can be called on the server easily.

Server looks for a matching route and a getInitialState method, if so calls it

We wait for API calls to be done then add data to context passed into StaticRouter

Back in the Route component, we look for that data and merge into state in the constructor.

staticContext is provided by React router withRouter()

SSR Server Seed Review

OK great, but now we are wasting our API call on the frontend, how do we prevent that?

We’ll put that same initialState in our index.html so on the client side, we can also seed that data and avoid the API call

Step 4: SSR API

Branch: step-4-api

SSR API Code Changes

  • Add a placeholder in index.html we can stuff our initial state into

  • Check for this existing data in our static getInitialState() method

  • Delete the data after we use it so other pages don’t use it

Code diff - step 3 vs step 4

SSR API - API Call Saving

Add a variable in index.html

Server will stuff initialState into that var

HeroList getInitialState(matchParams) checks for existing data

React Server Side Rendering Summary

  • Demo of React SSR from create react app without ejecting webpack config

  • Showed how to use SSR on an app that has async API calls and dynamic title tags

  • Implemented where SSR is used and we also eliminate the client side API calls for max efficiency


Next Steps

  • Great opportunity to build a generic SSR React component for Route components to inherit from

  • Encapsulates all that window and staticContext stuff to make it easy on devs.

  • Put caching in front of SSR so that you aren’t running SSR on the page every time

  • React Suspense API has high hopes to make it easier to identify those async API calls and make SSR easier