Server side rendering with react, react-router, and express Server side rendering with react, react-router, and express express express

Server side rendering with react, react-router, and express


So, I ended up solving this one myself. The error I was getting was from an un-rendered nested component, which is why the js engine was complaining about a random < char.

And now to my express setup. For those who aren't aware of how react can be used with server-side rendering, it's fairly straightforward: Node or io.js can be used to call React's renderToString() method on a component and then sending that to the requesting client. You've probably heard the benefits this approach brings already, but for those who don't know:

  1. you get more SEO-friendliness, even though google can already execute JS in it's crawlers; this is pretty much just a safer bet
  2. Fallback for non-js situations. If your app script is loading slowly, you can still render the actual page to your client and not make them wait while staring at a blank screen. This also allows someone with JS disabled on their browser to still interact with your app for the most part; links will still work, forms can still submit, &c.
  3. You can get the additional benefits of code-sharing between the client and server. There's nothing necessarily incredible about this aside from the fact that complexity is decreased and, as such, you get all the benefits of decreased complexity (potentially less coupling, easier maintainability, greater simplicity in structure, isomorphic-ness, &c.)
  4. A further side benefit is the ability to use react-router's html5 history API instead of the annoying hash-fragment stuff you have to otherwise use.

You could even get crazy with this approach and handle things like placeholders for your app while it loads or provide other feedback mechanisms for a slow-loading state (a la Facebook while it loads).

The basic approach operates roughly in the following manner:

  1. Upon bootstrap, the node app instantiates a react-router instance based on routes.jsx
  2. Request goes to the server, which then uses express' req.path to provide a route string for react-router to handle.
  3. React router then matches the provided route and tries to render the corresponding component for express to send back.
  4. React sends down the html response and your client gets to paint something regardless of the speed of your app script. We serve ours over a great CDN, but even with the best distribution and compression slow networks would still otherwise leave people with a temporarily blank screen.
  5. Having loaded the needed app script, React can use the same routes.jsx file to take over and generate html with react-router from here on out. Another benefit here is that your app code can be cached and future interactions hopefully won't even have to rely on another call.

One more point worth noting: I use webpack to bundle my react code and now browser.jsx is the entry point. Before refactoring for server-side rendering it was previously app.jsx; you might need to re-configure your structure to accommodate what gets rendered where. :)

le Code:

Browser.jsx

const React = require('react');const Router = require('react-router').Router;const hist = require('history');const routes = require('./routes');const newHistory = hist.createHistory();React.render(<Router history={newHistory}>{routes}</Router>, window.document);

App.js (express server):

//...other express configurationconst routes = require('../jsx/routes');const React = require('react');const {RoutingContext, match} = require('react-router');const hist = require('history');app.use((req, res, next) => {  const location = hist.createLocation(req.path);  match({    routes: routes,    location: location,  }, (err, redirectLocation, renderProps) => {    if (redirectLocation) {      res.redirect(301, redirectLocation.pathname + redirectLocation.search);    } else if (err) {      console.log(err);      next(err);      // res.send(500, error.message);    } else if (renderProps === null) {      res.status(404)        .send('Not found');    } else {      res.send('<!DOCTYPE html>' + React.renderToString(<RoutingContext {...renderProps}/>));    }  });});    //...other express configuration

Routes.jsx

<Route path="/" component={App}>  <DefaultRoute component={Welcome}/>  <Route path="dashboard" component={Dashboard}/>  <Route path="login" component={Login}/></Route>

App.jsx

<html><head>  <link rel="stylesheet" href="/assets/styles/app.css"/></head>  <body>    <Navigation/>    <RouteHandler/>    <Footer/>  <body/></html>

helpful links: