Server Side Rendering with React and express - JSCasts episode 14

In the previous episode, I showed you how you can add babel to your Node.js project. Now we will extend that example by adding Server Side Rendering with React.

As usual, code for the cast is available on GitHub. If you prefer watching instead of reading here is a youtube video:

The starting point

Look again at our modified application from the previous episode. We have a babel setup, and in .src directory, there is an express configuration and one controller rendering just a hello world message. In comparison with the previous episode I removed typescript to simplify things.

Simple SSR

So let’s say we want to render a Hello World message as an HTML page. To do this we might want to create a function which renders the HTML template. So let’s do that

Create a frontend directory and put the layout.js file there. This function, for now, will render a simple page with text as a variable. In the template, we will use an example from the Bulma CSS framework - to make it more pretty.

Copy the example HTML template from https://bulma.io/documentation/overview/start/

If you want to know more about Bulma - check out this page: https://bulma.io

Fix the indent and change Hallo world to a variable. Name it - helloMessage.

This is the layout function:

export default function (text) {
  return `
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Hello Bulma!</title>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.8.0/css/bulma.min.css">
        <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
      </head>
      <body>
      <section class="section">
        <div class="container">
          <h1 class="title">
            ${text}
          </h1>
          <p class="subtitle">
            My first website with <strong>Bulma</strong>!
          </p>
        </div>
      </section>
      </body>
    </html>
  `
}

Now, use it in our controller. Let’s wrap that with the function invocation:

async index (){
  return layout('Hello world')
}

Run the server with yarn dev command which we created in the previous episode. Open the page. And you see that it works. So we’ve just added Server Side Rendering on express.

Let’s spice things up and add React.js.

Adding React

In order to add React, we have to install a couple of things. First of all, we need “React” itself followed by react-dom. Next, we need a react babel preset.

yarn add --dev react react-dom @babel/preset-react

Ok, now we have to set up babel to properly handle React files.

First, add react preset. Next, set up babel to transpile react files with .jsx extension. We have to do this in babel/register and in the babel CLI.

If you would like to know how babel/register works and how to set up the build command - definitely you have to check out the previous episode.

Lastly, we need to update nodemon with --ext option. By default, it doesn’t check out the .jsx files as well.

"dev": "DEV_ENV=true nodemon --ext '.js,.jsx' index.js",

Ok. Now we are ready to create the first components.

Let’s start with the application component. Import react on top of the file so we can use react components.

So now, create a new variable and within braces place the HTML content. React uses className instead of class so let’s change that. And remove obsolete dollar sign.

The following code is transpiled to a javascript code, so we have to change that to a regular HTML. We can do this by running renderToString method form `react-dom/server`. So let’s do that.

Import it first. And store the result in the html variable.

Next, update the HTML layout to use it instead of hardcoded text. Wrap it with a div - we will need that later and Remove obsolete dollar sign.

This is our updated layout.js file

import React from 'react'
import { renderToString } from 'react-dom/server'
export default (helloMessage) => {

  const application = (
    <section className="section">
      <div className="container">
        <h1 className="title">
          {helloMessage}
        </h1>
        <p className="subtitle">
          My first website with <strong>Bulma</strong>!
        </p>
      </div>
    </section>
  )

  const html = renderToString(application)

  return `
  <!DOCTYPE html>
  <html>
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <title>Hello Bulma!</title>
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.8.0/css/bulma.min.css">
      <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
    </head>
    <body>
      <div id="root">${html}</div>
    </body>
  </html>
  `
}

Ok - refresh the page.

And now we see exactly the same content, but rendered with React.

Now, let’s make the app dynamic on the frontend - like a real React based application.

Refactor

But before we move there, let’s clean up our codebase. First, let’s extract the application to a separate file.

Create a folder called components, and put app.jsx file there, .JSX is an extension used by react components.

Import react, and create a functional component. We will fetch text from React props and export the App component.

import React from 'react'
const App = (props) => {
  const { text } = props
  return (
    <section className="section">
      <div className="container">
        <h1 className="title">
          This is the text
        </h1>
        <p className="subtitle">
          {text}
        </p>
      </div>
    </section>
  )
}
 
export default App

Now let’s update our layout function. Import the newly created component and pass the text in props.

Now move the inner content yet to another component, because we will make it dynamic.

Name it dynamic-box and just like before create a functional component

import React from 'react'
 
const DynamicBox = (props) => {
  const { text } = props
  return (
    <div className="container">
      <h1 className="title">
        This is the text
      </h1>
      <p className="subtitle">
        {text}
      </p>
    </div>
  )
}

export default DynamicBox

Now, update our app.jsx to use this component. We are passing all props.

Refresh the page to see if it still works.

Perfect!

React Hooks

Now let’s use react hooks to implement a dynamic counter.

With State hook create a counter and set its default value to 0.

Next, add a counter after the text. And add a button which will increment the counter. And set className to “button”

import React, { useState } from 'react'

const DynamicBox = (props) => {
  const { text } = props
  const [counter, setCounter] = useState(0)
  return(
    <div className="container">
      <h1 className="title">
        {text}, AwesomeMeter: {counter}
      </h1>
      <p className="subtitle">
        <button
          onClick={() => setCounter(counter+1)}
          className="button"
        >
          it is awesome
        </button>
      </p>
    </div>
  )
}

export default DynamicBox

When we visit the page again - we see that there is a button, but clicking it doesn’t do anything.

Making the app "dynamic" - Hydrate React App

In order to change that we have to “Hydrate” the component on the frontend. In regular react apps you “Render” components, but when the content is already rendered on the backend - you have to hydrate them.

We will use Parcel bundler in order to bundle all frontend files together and send them to the frontend app.

So, install parcel first

yarn add --dev parcel-bundler

Now create the entry file which we will bundle. It will be the equivalent of the application variable we used on the backend so let’s copy it.

Create entry.jsx file.

Paste the code from the layout.js. Change import statemen to ReactDOM and hydrate the app. The first argument is a React component, the second is a dom element. helloMessage is not accessible here so let’s stringify it for now.

import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/app'

const application = (
  <App text={"helloMessage"}/>
)

ReactDOM.hydrate(application, document.getElementById('root'))

In app.js file, we have to configure Parcel.

Import Parcel and point the new instance to the file we’ve just created. Use it as express middleware.

import Bundler from 'parcel-bundler'

const bundler = new Bundler('./src/frontend/entry.jsx')
...
app.use(bundler.middleware())

Now when we launch the server it will bundle the file to dist folder by default. Create it now and ignore it by nodemon because otherwise, we will have an infinite loop of reloads.

// package.json
scripts: {
...
    "dev": "DEV_ENV=true nodemon --ext '.js,.jsx' --ignore 'dist' index.js",
...
}

Now let’s pass the bundled file to the frontend. So let’s include script tag in HTML template on the bottom - and pass the entry file there will be loaded after the dom.

<script src="./entry.js"></script>

Refresh the page - and it works.

Passing State to the frontend

The last thing is the stringified helloMessage text.

To pass the data from the backend to the frontend you can use a global variable.

Go back to the layout function. Create a GLOBAL_STATE const and put there our text property.

Change the text prop to state and pass the new variable there.

Now pass it to the global variable on the frontend.

In the HEAD section, we can create a script tag and fill globalState with stringified data.

const GLOBAL_STATE = {
  text: helloMessage,
}
<script>
  GLOBAL_STATE = ${JSON.stringify(GLOBAL_STATE)};
</script>

We have to update the app component because now it takes the globalState from props.

And finally, update the entry file and pass GLOBAL_STATE.

const application = (
  <App globalState={GLOBAL_STATE}/>
)

Refresh the page - and it works. This way you can pass the entire state and feed, for example, redux with it.

Ok - this is everything. We’ve just updated an express app to render dynamic content with React. I hope you liked this episode - if you did, subscribe the channel and see you next time.