12. Improved Server Rendering

My first attempt to clean up the server rendering resulted in the following index.jsx file.

But be warned: this didn’t work

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

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

app.get('/', function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'})

  var elementCode = 'var timestampElement = React.render(
    timestampInstance, document.getElementById(\'reactContainer\'))'

  var html = React.renderToString(
    <html>
      <head>
      <title>Hello World</title>
      <script src="//fb.me/react-0.13.1.js"></script>
      <script src="/Components/Timestamp.js"></script>
      </head>
      <body>
      <HelloWorld from="index.jsx on the server" />
      <div id="reactContainer" />
      </body>
      <script>
      var timestampInstance = React.createFactory(Timestamp)();

      var timestampElement = React.render(timestampInstance,
        document.getElementById("reactContainer"));

      setInterval(function() { timestampElement.setState({
        date: "Updated through setState: " + new Date().toString() }) }, 500)
      </script>
    </html>)

  res.end(html)
})

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

Trying to run this results in a broken page and an error visible on the console:

ReferenceError: timestampElement is not defined

This confused me for a while, but after some investigation (removing some of the inline script and building it back up), I found that the JSX parser is trying to process the contents of the <script> tag. When the setInterval statement is evaluated, the { timestampElement.setState... is actually processed on the server!

Additionally, I saw that the document.getElementById("reactContainer") statement’s quotes were also getting escaped by the rendering, and I couldn’t find a straight-forward day to address that.

Ugh - so I’ve now learned that combining inline <script> tags and JSX is not a good recipe. We’ll need a different approach. We’ll go with a simple solution for the moment and just extract that code out into a separate JS file–one specifically for this page.

Let’s create a top-level assets folder and create this file as assets/index.js.

var timestampInstance = React.createFactory(Timestamp)();

var timestampElement = React.render(timestampInstance,
  document.getElementById("reactContainer"));

setInterval(function() {
  timestampElement.setState({
    date: "Updated through setState: " + new Date().toString() }) }, 500)

Then we’ll update our index.jsx file for Express to also serve static assets from our assets folder, and then we’ll change our inline <script> tag over to <script src="/assets/index.js"></script>.

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

var app = express()

app.use('/Components',
  express.static(path.join(path.join(__dirname, '..'),
  'Components')))

app.use('/assets',
  express.static(path.join(path.join(__dirname, '..'),
  'assets')))

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

    res.end(html)
})

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

Now the page is working again!

Next » Using JSX for Client Components