19. Reaching Isomorphic Goal

isomorphic

ADJECTIVE corresponding or similar in form and relations.

With React apps, people have been using the term ‘isomorphic’ to describe applications that:

  1. Render pages and components on the server, routing requests to the right views
  2. After page load, the browser creates the same components and takes over rendering and routing thereafter

React makes this approach really efficient because it puts checksums on DOM elements and when the client rendering is performed, any elements that have the same content as what the server generated will not be re-rendered in the DOM.

Getting to isomorphic behavior was the goal of this project; let’s see if we can do it now. We are already rendering the <HelloWorld> component on the server and separately on the client. Instead of having two instances on the page, we’ll combine them and achieve the isomorphic goal.

When the client calls React.render(), we need to give it the container element. For our client-side rendering of HelloWorld, we were using the <div id="reactHelloContainer"> element. Let’s just move our server-side rendering of HelloWorld into that container, and then the client-side rendering should take it over.

var React = require('react')
  , HelloWorld = require('./Components/HelloWorld')
  , express = require('express')
  , path = require('path')

var app = express()
app.use('/pages', express.static(path.join(__dirname, 'Pages')))

app.get('/', function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'})
  var html = React.renderToString(
    <html>
      <head>
        <title>Hello World</title>
      </head>
      <body>
        <div id="reactContainer" />
        <div id="reactHelloContainer">
          <HelloWorld from="server.jsx, running on the server" />
        </div>
      </body>
      <script src="/pages/index.js"></script>
    </html>)

    res.end(html)
})

app.listen(1337)
console.log('Server running at http://localhost:1337/')

Running the page at this point, we’ll see that we now have a single Timestamp element with a single HelloWorld component under that. The Timestamp element refreshes every second and we can still see its initial flicker. But for the HelloWorld component, we only see its client-side result without ever seeing the server’s message. So, did it work?

Well, the test for that is easy: View Source.

<html data-reactid=".1hcyssffzeo" data-react-checksum="1801386867">
  <head data-reactid=".1hcyssffzeo.0">
    <title data-reactid=".1hcyssffzeo.0.0">Hello World</title>
  </head>
  <body data-reactid=".1hcyssffzeo.1">
    <div id="reactContainer" data-reactid=".1hcyssffzeo.1.0"></div>
    <div id="reactHelloContainer" data-reactid=".1hcyssffzeo.1.1">
      <div data-reactid=".1hcyssffzeo.1.1.0">
        <div data-reactid=".1hcyssffzeo.1.1.0.0">
          This is from the HelloWorld.jsx component's render function.
        </div>
        <div data-reactid=".1hcyssffzeo.1.1.0.1">
          <span data-reactid=".1hcyssffzeo.1.1.0.1.0">
            Rendered from:
          </span>
          <span data-reactid=".1hcyssffzeo.1.1.0.1.1">
            server.jsx, running on the server
          </span>
        </div>
      </div>
    </div>
  </body>
  <script src="/pages/index.js" data-reactid=".1hcyssffzeo.2"></script>
</html>

We can see that the HelloWorld component clearly contained the “server.jsx, running on the server” message. But then when the /pages/index.js script ran, the component was re-rendered with the result of the client component. Sweet!

This is now an isomorphic app!

For an illustration to prove that this is working, you can disable JavaScript (Chrome’s developer tools has a handy checkbox for that), refresh the page, and see only the server-rendered HelloWorld message. The Timestamp won’t render and the HelloWorld message won’t change to the client’s message.

When you open up the JavaScript console though, you might see that we actually have a warning from React:

Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
 (client) reactid=".egwbkqfnr4"><div data-reactid=
 (server) reactid=".egwbkqfnr4.1.1.0"><div data-re

Uh-oh. We actually have one final problem. Our isomorphic application is not getting the benefits of a cheap client-side re-render because the component tree is different between the server and the client. We’ll complete one last cleanup step to fix this though.

Next » Final Cleanup