Server-side rendering with Express and React

If you’ve built a web application with React, chances are you’ve seen people discussing server side rendering. For whatever reason, there are lot of articles on the subject, which make a seemingly simple subject complex.

Let’s say you’ve made a simple message box:

import React from 'react'
import { render } from 'react-dom'
 
let Message =
  React.createClass({
    render: function () {
      return <div>{self.props.message}</div>
    };
  }
 
render(
  <Message message="Hello World" />,
  document.getElementById('container')
)

This is a pretty complex way to just render a message in a div, but in real applications these get a lot of logic quickly. All of this logic gets run in the user’s browser, or perhaps a search engine spider. You could just give the user an HTML page with the div pre-rendered…or you could add a translation layer so the user sees HTML pages, but you can still write logic with React.

If do you render this ahead of time, it saves the end user some time. On websites where the same content may be viewed many times, you can front this with something like Cloudflare and have a lot of pages cached.

To do this, your application needs two entry points – client and server side (the client side entry point is your normal application). With server side rendering, you collect any data you need to render the page, then use a handy React helper method to turn it into HTML:

let ReactDOMServer = require('react-dom/server');
let message = "My Message";
let html = ReactDOMServer.renderToString(<Message message={message} />);

If this is just a portion of a page (e.g. a div) you can insert it into the master page using some templating language (like ejs):

router.get("/test/:message', function(req, res) {
   ...
 
   res.render('example.ejs', {appHtml: html});
})

When you set up the page this renders into, you need to make sure there is no extra whitespace, or React will be unhappy:

<div id="container"><%- appHtml %></div>

Once the page loads, you’ll want to make sure that the “render” function of React gets called again.

This is typically a separate path into the application – you have a generic data-less entry point, and an entry point with data. If they use the same page / code / ids, it can transfer from pre-rendered to dynamic as the user starts to use the application. That said, some code is only available on the client side – if you’re using jQuery ajax, you likely have a place where you need to inject data, rather than determining it as the page renders.

If you’ve decided to this, there are several things you have to consider.

On the React side, you need some way to handle various URLs – React Router is a popular way to map URLs to screens. The following will take anything after “/content/” and send it to our Hello World component (this happens in the React rendering, rather than the Express.js routing):

import { Route } from 'react-router'
 
<Route path="/content/:message" component={Message}>
</Route>

If you use URL hashes (the “www.garysieling.com/test#message”), and you want a search engine to look at the hash, you need to use #!. For example, www.garysieling.com/test#!message.

The nice this about React Router it runs whether the code is running in a browser or in Node (unlike anything you write in Express.js).

If you’ve build an isomorphic application without server side rendering and want to add it, you’ll have a few steps ahead of you. I found that it was important to add ‘use strict’; everywhere if you are using ES6 Javascript methods (these seem to be enforced much more harshly in Node than browsers).

I’ve also found that it was preferable to remove “import” in favor of require (this may be an issue with my setup). The key issue this highlights is that you absolutely need a way to use the same version of Javascript on both the client and server side. The most obvious issue with React is JSX. To get this working in Node, you need a simple import at the top of your Express script:

require("node-jsx").install({extension: '.jsx'});

Most other issues you can solve with feature detection. For example if you’re using hot loading with webpack, the code to enable hot loading doesn’t work in server side rendering, but it’s easily fixed:

if (!!module.hot) {
  // only works client side...
  module.hot.accept();
}

I found it helpful to add a boolean attribute to my application to specify client vs. server mode – since there are separate entry points this is easy to set. This allows disabling ajax calls, and the like.

Finally, to do this you’ll need to make sure your application can accept rendering data as props, rather than internally maintained state. This is potentially the most difficult (and seemingly most controversial thing in React, next to JSX).

All-in-all, you can typically add server side rendering with minimal code changes, although this depends how far off your design is from the start. Typically I’ve found these design changes are more desirable anyway, as they make the React components that are essentially a function that transforms data to HTML, which is more the functional programming way. If the application is composed of many nested components, it then becomes very Visual Basic like, which makes for a fairly pleasant experience.

If you want a good Javascript book, I got a lot out of Secrets of the Javascript Ninja.

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *