Rendering React without browser JavaScript. The React Tutorial (github), modified to include Server-Side Rendering, React Router, and Redux (github).

Note: If you want to (1) Hire me, or (2) Feature an ad for your developer product within this article, email me: firasd at gmail

Universal React: render on the server before handing off to the browser (image)

Imagine creating a web app in cutting-edge React, only to discover it feels sluggish as users wait for all the JavaScript to download before they can see anything, and what’s more, amateur blogs are better search-engine optimized than your new site. In the inimitable words of DJ Khaled, “Congratulations, you played yourself.”

Fortunately, React provides a way to avoid these issues: rendering on the server.

You can use this method to generate HTML on the server and send the markup down on the initial request for faster page loads and to allow search engines to crawl your pages for SEO purposes.

Perfect. To render React on the server, our example app will handle requests using routes, and respond with markup generated using React components and the app state. When the app loads in the browser, we will use the same routes, components, and state to initialize React, handing off rendering from the server to the browser.

Back to the Comment Box

The official React tutorial demonstrates how to build a “simple but realistic comments box.” In my previous article, I modified the Comment Box code to demonstrate Redux usage: Quick Start Tutorial: React Redux. If you are new to React, I recommending checking out those tutorials first.

This tutorial for Universal React builds on the Comment Box, with Redux already integrated, to demonstrate Server-Side Rendering and React Router. The code for this revised example app is on Github.

Setting Up

Since universal React apps need to run JavaScript on the server, the first change I made to the official tutorial was to remove server scripts for other languages (PHP, Ruby, etc.), and to add more modules to the node.js app:

react, react-dom: to create and render React components on the server.

redux, react-redux: to manage the state of application data.

react-router: to demonstrate React Router usage.

marked: since the original Comment Box tutorial uses Markdown to format text in the browser, we need the same library to format text on the server.

babel-register, babel-preset-react: to compile JSX syntax, which is useful when writing React components, into standard JavaScript.

I added a polyfill (polyfill.js) that ensures browser support for Object.assign, a method used by our Redux reducer.

I also wrote a require-shims.js file which intercepts node.js require and module.exports in the app when node modules are loaded in the browser. This is somewhat absurd, and is only a stop-gap to let you check out and run the example app without having to install webpack just yet. More about webpack later in this tutorial.

Routes

The example app uses the Express web framework. Routing in the app is primarily handled by Express, with just two routes passed on to React Router: the default route “/”, and “/another-page”:

A nice aspect of React Router is that it’s declarative: routes express the way our components are structured. The Index component, which renders basic layout, wraps around two child components, CommentBox and AnotherPage.

In the webpage, we link to these URLs, “/” and “/another-page”, using the React Router Link component, which lets users transition instantly between pages in the browser:

You can learn more at the React Router docs.

Components

As our routes attest, we are rendering React components called Index, CommentBox, and AnotherPage. Index (index.js) is the index.html from the original tutorial converted into a React component. CommentBox (commentbox.js) and its child components are used for displaying and adding comments. AnotherPage is a simple example component, defined right in routes.js for convenience.

Index and CommentBox, while similar to the original Comment Box app, have been modified to use Redux. Check out my previous tutorial to understand the code changes in CommentBox, and the reducer I extracted to redux-store.js.

State

The state in our example app is a JavaScript object with three properties: data, which is an array of comments, url, which is the form submission URL, and pollInterval, which specifies how long to wait before checking for new comments. On the server we define the object and then pass it to the Redux store:

This configureStore function calls Redux’s createStore with our reducer and initial state.

In the webpage, this state is converted to a JSON string, with script tags escaped for security, and then embedded in a HTML script tag as a variable called window.__INITIAL_STATE__:

The window.__INITIAL_STATE__ variable then populates the Redux store in the browser. Yeah, it’s a bit circular, but relatively straightforward considering we’re creating a webpage that is propelled into reconstructing itself, like a slinky on a treadmill.

Initial Render: Server and Client

Now that our routes, components and state are integrated, we’re ready to go.

First, on the server we call ReactDOMServer.renderToString and use Express to send the generated markup as a response:

In the browser, we parallel this using ReactDOM.render:

And we’re all set! Call ReactDOMServer.render and ReactDOM.render with the same components and initial state, and React will recognize that components don’t need to be re-rendered in the browser.

If you call ReactDOM.render() on a node that already has this server-​rendered markup, React will preserve it and only attach event handlers, allowing you to have a very performant first-​load experience.

With browser JavaScript disabled

The original Comment Box handles submissions using JavaScript. It checks that the input fields are filled out, then makes an Ajax request to send the new comment to the server, and receives JSON with the updated list of comments. What if we take server-side rendering to its logical conclusion, and make the app work even when JavaScript is disabled in the browser? This might be difficult for complex applications, but we can definitely implement it in our example app.

First I adjusted the comment form (in commentbox.js) to submit data without JavaScript by adding action and method attributes. On the server endpoint that receives new comments, I duplicated the client-side validation to ensure the input fields are not empty by wrapping the method to add comments in a conditional block.

And in the response, instead of only sending JSON, we use Express’ req.accepts to determine the kind of response to send. Ajax requests receive a JSON response, while regular HTML form submissions get redirected back to the Comments page.

Using Webpack

As mentioned earlier, require-shims.js is a brittle workaround to enable you to run the example app without webpack. However, as you make your own apps, you will need to use a bundling tool at some point, both to get rid of all the script tags and replace it with one bundle, and more importantly, to pre-compile JSX code because compiling it in the browser is slow.

To start using webpack in the example app, first edit index.js. You’ll see a block of scripts to comment out or delete when using webpack, and one script tag to include. Go ahead and remove the script tags in the upper block, and uncomment the script tag for the webpack bundle:

Then install webpack and babel-loader.

The repository already comes with a webpack.config.js that defines some configuration for webpack. All you’ll need to do is run it:

This should create a bundle.js file in the public/scripts directory. Run the app again (node server.js) and you should be all set! When you open the webpage it will use the bundled JavaScript, including pre-compiled JSX code.

More about Webpack at webpack-howto.

Voila

Now you have a sense of how to render React on the server. It can be intricate to put the moving parts together, but as long as you remember to run the initial render on the server, before rendering the same components with the same state again in the browser, you should be on the right track. As DJ Khaled says: “You smart.”

If you found this article valuable, Paypal tips are appreciated.

Further Reading

Discussion on Hacker News.

React Comment box tutorial

React Comment Box tutorial: Tutorial, Github

Redux-enabled Comment Box: Tutorial, Github

Server-Side Rendered Comment Box: Github

More Examples

React Router

React Router documentation

Webpack

On Server-Side Rendering

Questions or feedback? Let me know in the comments.

I’m currently looking for projects or other opportunities in web development and product management roles. You can get in touch with me on Twitter, LinkedIn or firasd at gmail