Monday 10 April 2017

Server-Side Rendering with React, Express, React-Router

I am a beginner with React-Router and only about a year into Web development, for that matter. Because my routes return 404s when I directly access them versus using a Link or Redirect component, I am trying to implement server-side rendering.

I have looked through the documentation here: https://reacttraining.com/react-router/web/guides/server-rendering

and do not quite grok how it is that they're using ES6 syntax. My server.js file is in the root of my project and I don't think Node understands import or JSX markup.

I tried running my desired ES6 code through the online Babel transpiler and ended up in OK shape. Right now, my Express server looks like this in server.js:

// server.js

require('babel-register')({
      presets: ['es2015', 'react', 'stage-0']
})

var express = require('express')
var webpack = require('webpack')
var webpackDevMiddleware = require('webpack-dev-middleware')
var webpackHotMiddleware = require('webpack-hot-middleware')
var config = require('./webpack.config.js')
var compiler = webpack(config)
var react = require('react')
var react2 = _interopRequireDefault(react)
var server = require('react-dom/server')
var server2 = _interopRequireDefault(server)
var reactRouter = require('react-router')
var App = require('./app/components/App.jsx').default
var App2 = _interopRequireDefault(App)

function _interopRequireDefault (obj) { return obj && obj.__esModule ? obj : { default: obj } }


var app = express()
const PORT = process.env.PORT || 3003

app.use(function (req, res, next) {
  if (req.headers['x-forwarded-proto'] === 'https') {
    res.redirect('http://' + req.hostname + req.url)
  } else {
    next()
  }
})

app.use(webpackDevMiddleware(compiler, {
  publicPath: config.output.publicPath,
  stats: {
    colors: true
  },
  historyApiFallback: true
}))

app.use(webpackHotMiddleware(compiler, {
  log: console.log,
  path: '/__webpack_hmr',
  heartbeat: 10 * 1000
}))

app.use(express.static('public'))

app.use(function (req, res) {
  var context = {}

  var html = server2.default.renderToString(react2.default.createElement(
    reactRouter.StaticRouter,
    {
      location: req.url,
      context: context
    },
    react2.default.createElement(App2.default, null)
  ))

  if (context.url) {
    res.writeHead(301, {
      Location: context.url
    })
    res.end()
  } else {
    res.write('\n      <!doctype html>\n      <div id="app">' + html + '</div>\n    ')
    res.end()
  }
})

app.get('*', function (req, res) {
  res.sendFile(__dirname + '/index.html')
})

app.listen(PORT, function () {
  console.log('Express server is up on port ' + PORT)
})

The server comes up without an error and client-side routing is working correctly, but if I try to hit a route directly now, I get this error:

Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.

What am I not seeing here/doing incorrectly to render correctly on the server-side? Apologies in advance that the ES5 transpilation of the JSX/import statements is sort of a pain readability-wise.



via N.L.

No comments:

Post a Comment