Right now, I am working on marketing for CoBattery. In that vein, CoBattery is an iPhone battery case with a swappable battery. It comes with two batteries, so you never have to plug in your phone.

If you have an iPhone you should order one now! Hell, if you have a pulse and a credit card, you might as well order one.

OK, so now on to the content…


Google Cannot Index React

I am using Heroku for hosting, Flask for my production server and React to render HTML on the client side. So for React pages, Flask sends over a template that is as simple as:

<html>
  <head>...</head>
  <body>
    <div id="reactRoot" />
    <script src="/bundle.js"></script>
  </body>
</html>

This all seems fine, but there is one big problem: these pages cannot be indexed by Google. (You can use the Google search console to render a page as Google.)

The Google crawler does not handle React reliably, so when the crawler gets a React page, it will appear blank. So instead of my beautiful order page, the Google crawler just sees the above HTML which has no content.

This means that all of the work to build out the website is going to be totally ignored by Google. For discoverability and SEO, that’s a disaster.

Adding Node to Heroku

In order to get our site properly indexed by Google, I used Server Side Rendering. That means I am going to run React on the server to render the HTML and send that initial HTML to the client.

I use React Router and they have a pretty helpful overview page on server side rendering. React Router allows you to easily bind the client to the HTML that got loaded from the server. This allows you to load a page and continue using React without any errors.

My first problem was that my Heroku server did not have node installed because it is using the Python buildpack. But Heroku allows you to add buildpacks, so I added the Node buildpack by running:

heroku buildpacks:add -i 2 heroku/nodejs

Calling Through to Node in Flask

Now that we have Node on the server, we can use the command line to ferry data between Flask and Node.

First things first, we will need a Node script that will generate the HTML that we want. I use Webpack, so I bundled all of my application javascript with a new entry point called server.js.

// server.js

import React from 'react';
import {renderToString} from 'react-dom/server'
import {match, RouterContext} from 'react-router'

import routes from './routes'

const url = process.argv[2];


const generateHtmlFromReact = (url) => {
  // Match the routes to the url
  return match({routes: routes, location: url}, (err, redirect, props) => {

    // `RouterContext` is what the `Router` renders. `Router` keeps these
    // `props` in its state as it listens to `browserHistory`. But on the
    // server our app is stateless, so we need to use `match` to
    // get these props before rendering.
    const appHtml = renderToString(<RouterContext {...props}/>);

    console.log(appHtml);
  });
};

generateHtmlFromReact(url);

Then in Python world, I can use subprocess to call through to Node, like so:

from flask import render_template, request
import subprocess

def render_react():
    command_line_args = ['node', 'static/js/server.js', request.path]
    process = subprocess.Popen(command_line_args)
    body_html = process.communicate()[0].decode('utf-8').strip()

    return render_template('react_base.html', body_html=body_html)

Potential Issues

There are a few other problems that I ran into:

window is not defined in Node

So if you have window in render, getInitialState or componentDidMount, Node will not be able to render your HTML. You can get around this by checking typeof window === ‘undefined’.

In my top level App component, I defined the context parameter isRunningInBrowser so that is threaded through all of my components.

Some of your dependencies may not work in node

You can fix this by waiting to require them where they are used, and not requiring them if you are in Node.


Hopefully this helps someone! :)

I had a lot of trouble doing this initially when Google-ing around. And when I first looked into server side rendering it looked like a rabbit-hole that I was never going to make it out of.

If you have any questions/comments, you can send them my way at [email protected].