Tutorial

React on the Server for Beginners: Build a Universal React and Node App

Draft updated on Invalid Date
Default avatar

By Luciano Mammino

React on the Server for Beginners: Build a Universal React and Node App

This tutorial is out of date and no longer maintained.

Introduction

In this article we are going to learn how to build a simple “Universal JavaScript” application (a.k.a. “Isomorphic”) using React, React Router, and Express.

Warm up your fingers and get your keyboard ready… it’s going to be fun!

About the Author

Hi, I am Luciano and I am the co-author of Node.js Design Patterns Second Edition (Packt), a book that will take you on a journey across various ideas and components, and the challenges you would commonly encounter while designing and developing software using the Node.js platform. In this book, you will discover the “Node.js way” of dealing with design and coding decisions. It also features an entire chapter dedicated to Universal JavaScript. If this is the first time you read this term, keep reading, you are going to love this article!

About Universal JavaScript

One of the advantages of having Node.js as a runtime for the backend of a web application is that we have to deal only with JavaScript as a single language across the web stack. With this capability, it is totally legit to be willing to share some code between the frontend and the backend to reduce the code duplication between the browser and the server to the bare minimum.

The art of creating JavaScript code that is “environment agnostic” is today being recognized as “Universal JavaScript”, a term that — after a very long debate — seems to have won a war against the original name “Isomorphic JavaScript”.

The main concerns that we generally have to face when building a Universal JavaScript application are:

  • Module sharing: how to use Node.js modules also in the browser.
  • Universal rendering: how to render the views of the application from the server (during the initialization of the app) and then keep rendering the other views directly in the browser (avoiding a full page refresh) while the user keeps navigating across the different sections.
  • Universal routing: how to recognize the view associated with the current route from both the server and the browser.
  • Universal data retrieval: how to access data (typically through APIs) from both the server and the browser.

Universal JavaScript is still a pretty fresh field and there is no framework or approach that emerged as a “de-facto” standard with ready-made solutions for all these problems yet. Although, there is already a myriad of stable and well-known libraries and tools that can be combined to successfully build a Universal JavaScript web application.

In this article, we are going to use React (with its companion library React Router) and Express to build a simple application focused on showcasing universal rendering and routing. We will also use Babel to take advantage of the lovely EcmaScript 2015 syntax and Webpack to build our code for the browser.

What we are going to build

I am a Judo fan and so the app we are going to build today is “Judo Heroes”, a web app that showcases some the most famous Judo athletes and their collection of medals from the Olympic Games and other prestigious international tournaments.

This app has essentially two views:

An index page where you can select the athletes:

Universal JavaScript demo app "Judo Heroes" - Athlete selection view

And an athlete page that showcases their medals and some other details:

Universal JavaScript demo app "Judo Heroes" - Athlete details view

To understand better how it works you can have a look at the demo app and navigate across the views.

What’s the matter with it anyway, you are probably asking yourself! Yes, it looks like a very simple app, with some data and a couple of views…

Well there’s something very peculiar that happens behind the scenes that will be hardly noticed by a regular user but it makes development super interesting: this app is using universal rendering and routing!

We can prove this using the developers’ tools of the browser. When we initially load a page in the browser the server provides the full HTML code of the view and the browser only needs to download linked resources (images, stylesheets, and scripts):

Universal JavaScript demo app "Judo Heroes" - Server-side rendering resources

Then, from there, when we switch to another view, everything happens only on the browser: no HTML code is loaded from the server, and only the new resources (3 new images in the following example) are loaded by the browser:

Universal JavaScript demo app "Judo Heroes" - Client-side rendering resources

We can do another quick test (if you are still not convinced) from the command line using curl:

  1. curl -sS "https://judo-heroes.herokuapp.com/athlete/teddy-riner"

You will see the full HTML page (including the code rendered by React) being generated directly from the server:

Universal JavaScript demo app "Judo Heroes" - Server-side rendering with curl

I bet you are now convinced enough and eager to get your hands dirty, so let’s start coding!

Folder structure

At the end of this tutorial our project structure will look like in the following tree:

├── package.json
├── webpack.config.js
├── src
│   ├── app-client.js
│   ├── routes.js
│   ├── server.js
│   ├── components
│   │   ├── AppRoutes.js
│   │   ├── AthletePage.js
│   │   ├── AthletePreview.js
│   │   ├── AthletesMenu.js
│   │   ├── Flag.js
│   │   ├── IndexPage.js
│   │   ├── Layout.js
│   │   ├── Medal.js
│   │   └── NotFoundPage.js
│   ├── data
│   │   └── athletes.js
│   ├── static
│   │   ├── index.html
│   │   ├── css
│   │   ├── favicon.ico
│   │   ├── img
│   │   └── js
│   └── views
 `       └──  index.ejs

In the main level, we have our package.json (to describe the project and define the dependencies) and webpack.config.js (Webpack configuration file).

All the rest of the code will be stored inside the folder src, which contains the main files needed for routing (routes.js) and rendering (app-client.js and server.js). It also contains 4 subfolders:

  • components: contains all the React components
  • data: contains our data “module”
  • static: contains all the static files needed for our application (CSS, JS, images, etc.) and an index.html that we will use initially to test our app.
  • views: contains the template that we will use from the server to render the HTML content from the server.

Project initialization

The only requisite here is to have Node.js (version 6 is preferred) and NPM installed in your machine.

Let’s create a new folder called judo-heroes somewhere in the disk and point the terminal there, then launch:

  1. npm init

This will bootstrap our Node.js project allowing us to add all the needed dependencies.

We will need to have babel, ejs, express, react and react-router installed. To do so you can run the following command:

  1. npm install --save babel-cli@6.11.x babel-core@6.13.x \
  2. babel-preset-es2015@6.13.x babel-preset-react@6.11.x ejs@2.5.x \
  3. express@4.14.x react@15.3.x react-dom@15.3.x react-router@2.6.x

We will also need to install Webpack(with its Babel loader extension) and http-server as development dependencies:

  1. npm install --save-dev webpack@1.13.x babel-loader@6.2.x http-server@0.9.x

The HTML boilerplate

From now on, I am assuming you have a basic knowledge of React and JSX and its component-based approach. If not you can read an excellent article on React components or have a look at all the other React-related articles on Scotch.io.

Initially, we will focus only on creating a functional “Single Page Application” (with only client-side rendering). Later we will see how to improve it by adding universal rendering and routing.

So the first thing we need is an HTML boilerplate to “host” our app that we will store in src/static/index.html:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Judo Heroes - A Universal JavaScript demo application with React</title>
    <link rel="stylesheet" href="/css/style.css">
  </head>
  <body>
    <div id="main"></div>
    <script src="/js/bundle.js"></script>
  </body>
</html>

Nothing special here. Only two main things to underline:

  • We are using a simple “hand-made” stylesheet that you might want to download and save it under src/static/css/.
  • We also reference a /js/bundle.js file that contains all our JavaScript frontend code. We will see later in the article how to generate it using Webpack and Babel, so you don’t need to worry about it now.

The data module

In a real-world application, we would probably use an API to obtain the data necessary for our application.

In this case, we have a very small dataset with only 5 athletes and some related information, so we can keep things simple and embed the data into a JavaScript module. This way we can easily import the data into any other component or module synchronously, avoiding the added complexity and the pitfalls of managing asynchronous APIs in a Universal JavaScript project, which is not the goal of the article.

Let’s see how the module looks like:

// src/data/athletes.js
const athletes = [
  {
    'id': 'driulis-gonzalez',
    'name': 'Driulis González',
    'country': 'cu',
    'birth': '1973',
    'image': 'driulis-gonzalez.jpg',
    'cover': 'driulis-gonzalez-cover.jpg',
    'link': 'https://en.wikipedia.org/wiki/Driulis_González',
    'medals': [
      { 'year': '1992', 'type': 'B', 'city': 'Barcelona', 'event': 'Olympic Games', 'category': '-57kg' },
      { 'year': '1993', 'type': 'B', 'city': 'Hamilton', 'event': 'World Championships', 'category': '-57kg' },
      { 'year': '1995', 'type': 'G', 'city': 'Chiba', 'event': 'World Championships', 'category': '-57kg' },
      { 'year': '1995', 'type': 'G', 'city': 'Mar del Plata', 'event': 'Pan American Games', 'category': '-57kg' },
      { 'year': '1996', 'type': 'G', 'city': 'Atlanta', 'event': 'Olympic Games', 'category': '-57kg' },
      { 'year': '1997', 'type': 'S', 'city': 'Osaka', 'event': 'World Championships', 'category': '-57kg' },
      { 'year': '1999', 'type': 'G', 'city': 'Birmingham', 'event': 'World Championships', 'category': '-57kg' },
      { 'year': '2000', 'type': 'S', 'city': 'Sydney', 'event': 'Olympic Games', 'category': '-57kg' },
      { 'year': '2003', 'type': 'G', 'city': 'S Domingo', 'event': 'Pan American Games', 'category': '-63kg' },
      { 'year': '2003', 'type': 'S', 'city': 'Osaka', 'event': 'World Championships', 'category': '-63kg' },
      { 'year': '2004', 'type': 'B', 'city': 'Athens', 'event': 'Olympic Games', 'category': '-63kg' },
      { 'year': '2005', 'type': 'B', 'city': 'Cairo', 'event': 'World Championships', 'category': '-63kg' },
      { 'year': '2006', 'type': 'G', 'city': 'Cartagena', 'event': 'Central American and Caribbean Games', 'category': '-63kg' },
      { 'year': '2006', 'type': 'G', 'city': 'Cartagena', 'event': 'Central American and Caribbean Games', 'category': 'Tema' },
      { 'year': '2007', 'type': 'G', 'city': 'Rio de Janeiro', 'event': 'Pan American Games', 'category': '-63kg' },
      { 'year': '2007', 'type': 'G', 'city': 'Rio de Janeiro', 'event': 'World Championships', 'category': '-63kg' },
    ],
  },
  {
    // ...
  }
];

export default athletes;

For brevity, the file here has been truncated, and we are displaying just the data of one of the five athletes. If you want to see the full code check it out on the official repository. You can download the file into src/data/athletes.js.

Also notice that I am not reporting 'use strict'; here though it should be present in every JavaScript file we are going to create through the course of this tutorial.

As you can see, the file contains an array of objects where every object represents an athlete containing some generic information like id, name, and country and another array of objects representing the medals won by that athlete.

You might also want to grab all the image files from the repository and copy them under: src/static/img/.

React components

We are going to organize the views of our application into several components:

  • A set of small UI components used to build the views: AthletePreview, Flag, Medal, and AthletesMenu.
  • A Layout component that is used as a master component to define the generic appearance of the application (header, content, and footer blocks).
  • Two main components that represent the main sections: IndexPage and AthletePage.
  • An extra “page” component that we will use as 404 page: NotFoundPage
  • The AppRoutes component that uses React Router to manage the routing between views.

Flag component

The first component that we are going to build allows us to display a nice flag and, optionally, the name of the country that it represents:

// src/components/Flag.js
import React from 'react';

const data = {
  'cu': {
    'name': 'Cuba',
    'icon': 'flag-cu.png',
  },
  'fr': {
    'name': 'France',
    'icon': 'flag-fr.png',
  },
  'jp': {
    'name': 'Japan',
    'icon': 'flag-jp.png',
  },
  'nl': {
    'name': 'Netherlands',
    'icon': 'flag-nl.png',
  },
  'uz': {
    'name': 'Uzbekistan',
    'icon': 'flag-uz.png',
  }
};

export default class Flag extends React.Component {
  render() {
    const name = data[this.props.code].name;
    const icon = data[this.props.code].icon;
    return (
      <span className="flag">
        <img className="icon" title={name} src={`/img/${icon}`}/>
        {this.props.showName && <span className="name"> {name}</span>}
      </span>
    );
  }
}

As you might have noticed this component uses a small array of countries as a data source. Again this makes sense only because we need a very small data set which, for the sake of this demo app, is not going to change. In a real application with a larger and more complex data set you might want to use an API or a different mechanism to connect the data to the component.

In this component, it’s also important to notice that we are using two different props, code and showName. The first one is mandatory and must be passed to the component to select which flag will be shown among the ones supported. The showName prop is instead optional and if set to a truthy value the component will also display the name of the country just after the flag.

If you want to build a more refined reusable component for a real-world app you might also want to add to it props validation and defaults, but we are going to skip this step here as this is not the goal for the app we want to build.

Medal component

The Medal component is similar to the Flag component. It receives some props that represent the data related to a medal: the type (G for gold, S for silver and B for bronze), the year when it was won, the name of the event and the city where the tournament was hosted and the category where the athlete who won the medal competed.

// src/components/Medal.js
import React from 'react';

const typeMap = {
  'G': 'Gold',
  'S': 'Silver',
  'B': 'Bronze'
};

export default class Medal extends React.Component {
  render() {
    return (
      <li className="medal">
        <span className={`symbol symbol-${this.props.type}`} title={typeMap[this.props.type]}>{this.props.type}</span>
        <span className="year">{this.props.year}</span>
        <span className="city"> {this.props.city}</span>
        <span className="event"> ({this.props.event})</span>
        <span className="category"> {this.props.category}</span>
      </li>
    );
  }
}

As for the previous component here we also use a small object to map the codes of the medal types to descriptive names.

Athletes Menu component

In this section we are going to build the menu that is displayed on top of every athlete page to allow the user to easily switch to another athlete without going back to the index:

// src/components/AthletesMenu.js
import React from 'react';
import { Link } from 'react-router';

export default class AthletesMenu extends React.Component {
  render() {
    return (
      <nav className="athletes-menu">
        {this.props.athletes.map(menuAthlete => {
          return <Link key={menuAthlete.id} to={`/athlete/${menuAthlete.id}`} activeClassName="active">
            {menuAthlete.name}
          </Link>;
        })}
      </nav>
    );
  }
}

The component is very simple, but there are some key points to underline:

  • We are expecting the data to be passed in the component through a athletes prop. So from the outside, when we use the component in our layout, we will need to propagate the list of athletes available in the app directly into the component.
  • We use the map method to iterate over all the athletes and generate for every one of them a Link.
  • Link is a special component provided by React Router to create links between views.
  • Finally, we use the prop activeClassName to use the class active when the current route matches the path of the link.

Athlete Preview component

The AthletePreview component is used in the index to display the pictures and the names of the athletes. Let’s see its code:

// src/components/AthletePreview.js
import React from 'react';
import { Link } from 'react-router';

export default class AthletePreview extends React.Component {
  render() {
    return (
      <Link to={`/athlete/${this.props.id}`}>
        <div className="athlete-preview">
          <img src={`img/${this.props.image}`}/>
          <h2 className="name">{this.props.name}</h2>
          <span className="medals-count"><img src="/img/medal.png"/> {this.props.medals.length}</span>
        </div>
      </Link>
    );
  }
}

The code is quite simple. We expect to receive a number of props that describe the attributes of the athlete we want to display like id, image, name, and medals. Note that again we are using the Link component to create a link to the athlete page.

Layout component

Now that we built all our basic components let’s move to create those that give the visual structure to the application. The first one is the Layout component, which has the only purpose of providing a display template to the whole application defining a header, a space for the main content, and a footer:

// src/components/Layout.js
import React from 'react';
import { Link } from 'react-router';

export default class Layout extends React.Component {
  render() {
    return (
      <div className="app-container">
        <header>
          <Link to="/">
            <img className="logo" src="/img/logo-judo-heroes.png"/>
          </Link>
        </header>
        <div className="app-content">{this.props.children}</div>
        <footer>
          <p>
            This is a demo app to showcase universal rendering and routing with <strong>React</strong> and <strong>Express</strong>.
          </p>
        </footer>
      </div>
    );
  }
}

The component is pretty simple and we should understand how it works just by looking at the code. There’s though a very interesting prop that we are using here, the children prop. This is a special property that React provides to every component and allows to nest components one inside another.

We are going to see in the routing section how the React Router will make sure to nest the components into the Layout component.

Index Page component

This component constitutes the full index page and it contains some of the components we previously defined:

// src/components/IndexPage.js
import React from 'react';
import AthletePreview from './AthletePreview';
import athletes from '../data/athletes';

export default class IndexPage extends React.Component {
  render() {
    return (
      <div className="home">
        <div className="athletes-selector">
          {athletes.map(athleteData => <AthletePreview key={athleteData.id} {...athleteData} />)}
        </div>
      </div>
    );
  }
}

Note that in this component we are using the AthletePreview component we created previously.

Basically, we are iterating over all the available athletes from our data module and creating an AthletePreview component for each of them. The AthletePreview component is data agnostic, so we need to pass all the information about the current athlete as props using the JSX spread operator ({...object}).

Athlete Page component

In a similar fashion we can define the AthletePage component:

// src/components/AthletePage.js
import React from 'react';
import { Link } from 'react-router';
import NotFoundPage from './NotFoundPage';
import AthletesMenu from './AthletesMenu';
import Medal from './Medal';
import Flag from './Flag';
import athletes from '../data/athletes';

export default class AthletePage extends React.Component {
  render() {
    const id = this.props.params.id;
    const athlete = athletes.filter((athlete) => athlete.id === id)[0];
    if (!athlete) {
      return <NotFoundPage/>;
    }
    const headerStyle = { backgroundImage: `url(/img/${athlete.cover})` };
    return (
      <div className="athlete-full">
        <AthletesMenu athletes={athletes}/>
        <div className="athlete">
          <header style={headerStyle}/>
          <div className="picture-container">
            <img src={`/img/${athlete.image}`}/>
            <h2 className="name">{athlete.name}</h2>
          </div>
          <section className="description">
            Olympic medalist from <strong><Flag code={athlete.country} showName="true"/></strong>,
            born in {athlete.birth} (Find out more on <a href={athlete.link} target="_blank">Wikipedia</a>).
          </section>
          <section className="medals">
            <p>Winner of <strong>{athlete.medals.length}</strong> medals:</p>
            <ul>{
              athlete.medals.map((medal, i) => <Medal key={i} {...medal}/>)
            }</ul>
          </section>
        </div>
        <div className="navigateBack">
          <Link to="/">« Back to the index</Link>
        </div>
      </div>
    );
  }
}

By now, you must be able to understand most of the code shown here and how the other components are used to build this view. What might be important to underline is that this page component accepts from the outside only the id of the athlete, so we include the data module to be able to retrieve the related information. We do this at the beginning of the render method using the function filter on the data set. We are also considering the case where the received id does not exist in our data module, in this case, we render NotFoundPage, a component that we are going to create in the next section.

One last important detail is that here we are accessing the id with this.props.params.id (instead of simply this.props.id): params is a special object created by React Router when using a component from a Route and it allows to propagate routing parameters into components. It will be easier to understand this concept when we will see how to set up the routing part of the application.

Not Found Page component

Now let’s see the NotFoundPage component, which acts as a template to generate the code of our 404 pages:

// src/components/NotFoundPage.js
import React from 'react';
import { Link } from 'react-router';

export default class NotFoundPage extends React.Component {
  render() {
    return (
      <div className="not-found">
        <h1>404</h1>
        <h2>Page not found!</h2>
        <p>
          <Link to="/">Go back to the main page</Link>
        </p>
      </div>
    );
  }
}

App Routes component

The last component we need to create is the AppRoutes component which is the master component that renders all the other views using internally the React Router. This component will use the routes module, so let’s have a quick look at it first:

// src/routes.js
import React from 'react'
import { Route, IndexRoute } from 'react-router'
import Layout from './components/Layout';
import IndexPage from './components/IndexPage';
import AthletePage from './components/AthletePage';
import NotFoundPage from './components/NotFoundPage';

const routes = (
  <Route path="/" component={Layout}>
    <IndexRoute component={IndexPage}/>
    <Route path="athlete/:id" component={AthletePage}/>
    <Route path="*" component={NotFoundPage}/>
  </Route>
);

export default routes;

In this file, we are basically using the React Router Route component to map a set of routes to the page components we defined before.

Note how the routes are nested inside a main Route component. Let’s explain how this works:

  • The root route maps the path / to the Layout component. This allows us to use our custom layout in every section of our application. The components defined into the nested routes will be rendered inside the Layout component in place of the this.props.children property that we discussed before.
  • The first child route is an IndexRoute which is a special route used to define the component that will be rendered when we are viewing the index page of the parent route (/ in this case). We use our IndexPage component as index route.
  • The path athlete/:id is mapped to the AthletePage. Note here that we are using a named parameter :id. So this route will match all the paths with the prefix /athlete/, the remaining part will be associated to the params id and will be available inside the component in this.props.params.id.
  • Finally the match-all route * maps every other path to the NotFoundPage component. This route must be defined as the last one.

Let’s see now how to use these routes with the React Router inside our AppRoutes component:

// src/components/AppRoutes.js
import React from 'react';
import { Router, browserHistory } from 'react-router';
import routes from '../routes';

export default class AppRoutes extends React.Component {
  render() {
    return (
      <Router history={browserHistory} routes={routes} onUpdate={() => window.scrollTo(0, 0)}/>
    );
  }
}

Basically, we only need to import the Router component and add it inside our render function. The router receives our routes mapping in the router prop. We also configure the history prop to specify that we want to use the HTML5 browser history for the routing (as an alternative you could also use hashHistory).

Finally, we also added an onUpdate callback to reset the scrolling of the window to the top every time a link is clicked.

The application entry point

The last bit of code to complete our first version of the application is to define the JavaScript logic that initializes the whole app in the browser:

// src/app-client.js
import React from 'react';
import ReactDOM from 'react-dom';
import AppRoutes from './components/AppRoutes';

window.onload = () => {
  ReactDOM.render(<AppRoutes/>, document.getElementById('main'));
};

The only thing we do here is to import our master AppRoutes component and render it using the ReactDOM.render method. The React app will be living inside our #main DOM element.

Setting up Webpack and Babel

Before we are able to run our application we need to generate the bundle.js file containing all our React components with Webpack. This file will be executed by the browser so Webpack will make sure to convert all the modules into code that can be executed in the most common browser environments. Webpack will convert ES2015 and React JSX syntax to equivalent ES5 syntax (using Babel), which can be executed practically by every browser. Furthermore, we can use Webpack to apply a number of optimizations to the resulting code like combining all the scripts files into one file and minifying the resulting bundle.

Let’s write our webpack configuration file:

// webpack.config.js
const webpack = require('webpack');
const path = require('path');

module.exports = {
  entry: path.join(__dirname, 'src', 'app-client.js'),
  output: {
    path: path.join(__dirname, 'src', 'static', 'js'),
    filename: 'bundle.js'
  },
  module: {
    loaders: [{
      test: path.join(__dirname, 'src'),
      loader: ['babel-loader'],
      query: {
        cacheDirectory: 'babel_cache',
        presets: ['react', 'es2015']
      }
    }]
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
    }),
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.optimize.UglifyJsPlugin({
      compress: { warnings: false },
      mangle: true,
      sourcemap: false,
      beautify: false,
      dead_code: true
    })
  ]
};

In the first part of the configuration file, we define what is the entry point and the output file. The entry point is the main JavaScript file that initializes the application. Webpack will recursively resolve all the included/imported resources to determine which files will go into the final bundle.

The module.loaders section allows specifying transformations on specific files. Here we want to use Babel with the react and es2015 presets to convert all the included JavaScript files to ES5 code.

In the final section we use plugins to declare and configure all the optimizations plugins we want to use:

  • DefinePlugin allows us to define the NODE_ENV variable as a global variable in the bundling process as if it was defined in one of the scripts. Some modules (e.g., React) rely on it to enable or disable specific features for the current environment (production or development).
  • DedupePlugin removes all the duplicated files (modules imported in more than one module).
  • OccurenceOrderPlugin helps in reducing the file size of the resulting bundle.
  • UglifyJsPlugin minifies and obfuscates the resulting bundle using UglifyJs.

Now we are ready to generate our bundle file, you just need to run:

  1. NODE_ENV=production node_modules/.bin/webpack -p

(if you are on Windows you can use PowerShell and run set NODE_ENV=production | node_modules/.bin/webpack -p. Thanks Miles Rausch for the suggestion)

The NODE_ENV environment variable and the -p option are used to generate the bundle in production mode, which will apply a number of additional optimizations, for example, removing all the debug code from the React library.

If everything went fine you will now have your bundle file in src/static/js/bundle.js.

Playing with the single page app

We are finally ready to play with the first version of our app!

We don’t have a Node.js web server yet, so for now we can just use the module http-server (previously installed as development dependency) to run a simple static file webserver:

  1. node_modules/.bin/http-server src/static

And your app will be magically available on http://localhost:8080.

Ok, now take some time to play with it, click on all the links and explore all the sections.

Does everything seem to work alright? Well, almost! There’s just a little caveat… If you refresh the page in a section different from the index you will get a 404 error from the server.

There are a number of ways to address this problem. In our case it will be solved as soon as we implement our universal routing and rendering solution, so let’s move on to the next section.

Routing and rendering on the server with Express

Ok, we are now ready to evolve our application to the next level and build the missing server-side part.

In order to have server-side routing and rendering, we will use Express with a relatively small server script that we will see in a moment.

The rendering part will use an ejs template as replacement for our index.html file that we will save in src/views/index.ejs:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Judo Heroes - A Universal JavaScript demo application with React</title>
    <link rel="stylesheet" href="/css/style.css">
  </head>
  <body>
    <div id="main"><%- markup -%></div>
    <script src="/js/bundle.js"></script>
  </body>
</html>

The only difference with the original HTML file is that we are using the template variable <%- markup -%> inside the #main div in order to include the React markup into the server-generated HTML code.

Now we are ready to write our server application:

// src/server.js

import path from 'path';
import { Server } from 'http';
import Express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { match, RouterContext } from 'react-router';
import routes from './routes';
import NotFoundPage from './components/NotFoundPage';

// initialize the server and configure support for ejs templates
const app = new Express();
const server = new Server(app);
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));

// define the folder that will be used for static assets
app.use(Express.static(path.join(__dirname, 'static')));

// universal routing and rendering
app.get('*', (req, res) => {
  match(
    { routes, location: req.url },
    (err, redirectLocation, renderProps) => {

      // in case of error display the error message
      if (err) {
        return res.status(500).send(err.message);
      }

      // in case of redirect propagate the redirect to the browser
      if (redirectLocation) {
        return res.redirect(302, redirectLocation.pathname + redirectLocation.search);
      }

      // generate the React markup for the current route
      let markup;
      if (renderProps) {
        // if the current route matched we have renderProps
        markup = renderToString(<RouterContext {...renderProps}/>);
      } else {
        // otherwise we can render a 404 page
        markup = renderToString(<NotFoundPage/>);
        res.status(404);
      }

      // render the index template with the embedded React markup
      return res.render('index', { markup });
    }
  );
});

// start the server
const port = process.env.PORT || 3000;
const env = process.env.NODE_ENV || 'production';
server.listen(port, err => {
  if (err) {
    return console.error(err);
  }
  console.info(`Server running on http://localhost:${port} [${env}]`);
});

The code is commented, so it shouldn’t be hard to get a general understanding of what is going on here.

The important part of the code here is the Express route defined with app.get('*', (req, res) => {...}). This is an Express catch-all route that will intercept all the GET requests to every URL in the server. Inside this route, we take care of delegating the routing logic to the React Router match function.

ReactRouter.match accepts two parameters: the first one is a configuration object and the second is a callback function. The configuration object must have two keys:

  • routes: used to pass the React Router routes configuration. Here, we are passing the exact same configuration that we used for the client-side rendering.
  • location: This is used to specify the currently requested URL.

The callback function is called at the end of the matching. It will receive three arguments, error, redirectLocation, and renderProps, that we can use to determine what exactly the result of the match operation was.

We can have four different cases that we need to handle:

  • The first case is when we have an error during the routing resolution. To handle this case, we simply return a 500 internal server error response to the browser.
  • The second case is when we match a route that is a redirect route. In this case, we need to create a server redirect message (302 redirect) to tell the browser to go to the new destination (this is not really happening in our application because we are not using redirect routes in our React Router configuration, but it’s good to have it ready in case we decide to keep evolving our application).
  • The third case is when we match a route and we have to render the associated component. In this case, the argument renderProps is an object that contains the data we need to use to render the component. The component we are rendering is RouterContext (contained in the React Router module), which is responsible for rendering the full component tree using the values in renderProps.
  • The last case is when the route is not matched, and here we can simply return a 404 not found error to the browser.

This is the core of our server-side routing mechanism and we use the ReactDOM.renderToString function to be able to render the HTML code that represents the component associated with the currently matched route.

Finally, we inject the resulting HTML into the index.ejs template we defined before to obtain the full HTML page that we send to the browser.

Now we are ready to run our server.js script, but because it’s using the JSX syntax we cannot simply run it with the node interpreter. We need to use babel-node and the full command (from the root folder of our project) looks like this:

  1. NODE_ENV=production node_modules/.bin/babel-node --presets react,es2015 src/server.js

Running the complete app

At this stage your app is available at http://localhost:3000 and, for the sake of this tutorial, it can be considered complete.

Again feel free to check it out and try all the sections and links. You will notice that this time we can refresh every page and the server will be able to identify the current route and render the right page.

Small advice: don’t forget to check out the 404 page by entering a random non-existing URL!

Conclusion

HOORAY! This completes our tutorial! I’m really happy to know you got to the end, but you can make me happier if you post here in the comments some examples of Universal JavaScript apps that you built using this (or a similar) approach.

If you want to know more about Universal Javascript and improve your application even more (e.g., by adding Universal Data Retrieval using REST APIs) I definitely recommend reading the chapter Universal JavaScript for Web Applications in my book Node.js Design Patterns:

Node.Js Design Patterns Second Edition by Mario Casciaro and Luciano Mammino book cover

Until next time!

PS: Huge thanks to Mario Casciaro for reviewing this article and to Marwen Trabelsi for the support on improving the code! Also thanks to @CriticalMAS for finding a typo.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
Luciano Mammino

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel