Server Side Rendering, also called SSR, is the ability of a JavaScript application to render on the server rather than in the browser.

Why would we ever want to do so?

  • it allows your site to have a faster first page load time, which is the key to a good user experience
  • it is essential for SEO: search engines cannot (yet?) efficiently and correctly index applications that exclusively render client-side. Despite the latest improvements to indexing in Google, there are other search engines too, and Google is not perfect at it in any case. Also, Google favors sites with fast load times, and having to load client-side is not good for speed
  • it’s great when people share a page of your site on social media, as they can easily gather the metadata needed to nicely share the link (images, title, description..)

Without Server Side Rendering, all your server ships is an HTML page with no body, just some script tags that are then used by the browser to render the application.

Client-rendered apps are great at any subsequent user interaction after the first page load. Server Side Rendering allows us to get the sweet spot in the middle of client-rendered apps and backend-rendered apps: the page is generated server-side, but all interactions with the page once it’s been loaded are handled client-side.

However Server Side Rendering has its drawback too:

  • it’s fair to say that a simple SSR proof of concept is simple, but the complexity of SSR can grow with the complexity of your application
  • rendering a big application server-side can be quite resource-intensive, and under heavy load it could even provide a slower experience than client-side rendering, since you have a single bottleneck

A very simplistic example of what it takes to Server-Side render a React app

SSR setups can grow very, very complex and most tutorials will bake in Redux, React Router and many other concepts from the start.

To understand how SSR works, let’s start from the basics to implement a proof of concept.

Feel free to skip this paragraph if you just want to look into the libraries that provide SSR and not bother with the ground work

To implement basic SSR we’re going to use Express.

If you are new to Express, or need some catch-up, check out my free Express Handbook here:

Warning: the complexity of SSR can grow with the complexity of your application. This is the bare minimum setup to render a basic React app. For more complex needs you might need to do a bit more work or also check out SSR libraries for React.

I assume you started a React app with create-react-app. If you are just trying, install one now using npx create-react-app ssr.

Go to the main app folder with the terminal, then run:

npm install express

You have a set of folders in your app directory. Create a new folder called server, then go into it and create a file named server.js.

Following the create-react-app conventions, the app lives in the src/App.js file. We’re going to load that component, and render it to a string using ReactDOMServer.renderToString(), which is provided by react-dom.

You get the contents of the ./build/index.html file, and replace the <div id="root"></div> placeholder, which is the tag where the application hooks by default, with `<div id="root">\${ReactDOMServer.renderToString(<App />)}</div>.

All the content inside the build folder is going to be served as-is, statically by Express.

import path from 'path'
import fs from 'fs'

import express from 'express'
import React from 'react'
import ReactDOMServer from 'react-dom/server'

import App from '../src/App'

const PORT = 8080
const app = express()

const router = express.Router()

const serverRenderer = (req, res, next) => {
  fs.readFile(path.resolve('./build/index.html'), 'utf8', (err, data) => {
    if (err) {
      return res.status(500).send('An error occurred')
    return res.send(
        '<div id="root"></div>',
        `<div id="root">${ReactDOMServer.renderToString(<App />)}</div>`
router.use('^/$', serverRenderer)

  express.static(path.resolve(__dirname, '..', 'build'), { maxAge: '30d' })

// tell the app to use the above rules

// app.use(express.static('./build'))
app.listen(PORT, () => {
  console.log(`SSR running on port ${PORT}`)

Now, in the client application, in your src/index.js, instead of calling ReactDOM.render():

ReactDOM.render(<App />, document.getElementById('root'))

call ReactDOM.hydrate(), which is the same but has the additional ability to attach event listeners to existing markup once React loads:

ReactDOM.hydrate(<App />, document.getElementById('root'))

All the Node.js code needs to be transpiled by Babel, as server-side Node.js code does not know anything about JSX, nor ES Modules (which we use for the include statements).

Install these 3 packages:

npm install @babel/register @babel/preset-env @babel/preset-react ignore-styles express

ignore-styles is a Babel utility that will tell it to ignore CSS files imported using the import syntax.

Let’s create an entry point in server/index.js:


  ignore: [/(node_modules)/],
  presets: ['@babel/preset-env', '@babel/preset-react']


Build the React application, so that the build/ folder is populated:

npm run build

and let’s run this:

node server/index.js

I said this is a simplistic approach, and it is:

  • it does not handle rendering images correctly when using imports, which need Webpack in order to work (and which complicates the process a lot)
  • it does not handle page header metadata, which is essential for SEO and social sharing purposes (among other things)

So while this is a good example of using ReactDOMServer.renderToString() and ReactDOM.hydrate to get this basic server-side rendering, it’s not enough for real world usage.

Server Side Rendering using libraries

SSR is hard to do right, and React has no de-facto way to implement it.

It’s still very much debatable if it’s worth the trouble, complication and overhead to get the benefits, rather than using a different technology to serve those pages. This discussion on Reddit has lots of opinions in that regard.

When Server Side Rendering is an important matter, my suggestion is to rely on pre-made libraries and tools that have had this goal in mind since the beginning.

In particular, I suggest Next.js and Gatsby, two projects we’ll see later on.