Arpith Siromoney 💬

Server Rendering with React Router

I build apps and websites, and lately I’ve been building isomorphic/universal websites with React. This is how you can use React Router to render on the server with Koa.

The basics are straightforward:

  1. Your Koa app uses React Router’s match()
  2. match() takes your routes and the url, and a callback
  3. The callback will either receive an error or a redirect location or render props

If you’ve got an error or a redirect you can use Koa’s this.throw and this.redirect, and if you’ve got nothing you can throw a 404. On the other hand, if you’ve got renderProps you can then pass React Router’s <RouterContext {…renderProps} /> to react-dom/server’s renderToStringfunction.

require('babel-core/register');
var ReactDOMServer = require('react-dom/server');
var ReactRouter = require('react-router');
var koa = require('koa');
var render = require('koa-ejs');
var path = require('path');
var RouterContext = require('./RouterContext');
var App = require('./components/App');
var app = koa();
render(app, {root: path.join(__dirname, 'view')});
app.use(function *() {
 const routes = {
   path: '/',
   component: App
 };

 ReactRouter.match({
   routes: routes,
   location: this.url
 }, (error, redirectLocation, renderProps) => {
   if (error) {
     this.throw(error.message, 500);
   } else if (redirectLocation) {
     this.redirect(redirectLocation.pathname + redirectLocation.search);
   } else if (renderProps) {
     var reactString = ReactDOMServer.renderToString(RouterContext(renderProps));
     this.render('layout', {react: reactString});
   } else {
     this.throw('Not Found', 404);
   }
 });
});
var port = process.env.PORT || 3000;
 app.listen(port);
});

A couple of things thrown in there:

  1. I’m using Babel’s require hook to support the jsx in my React component
  2. This doesn’t transpile the file it’s in, so I’m using plain routes
  3. This is also why I’ve placed <RouterContext /> in a separate file
  4. Finally, I’m using koa-ejs to provide the HTML around the react string.

My RouterContext.jsx looks like:

var React = require('react');
var RoutingContext = require('react-router').RoutingContext;
module.exports = (renderProps) => <RoutingContext {…renderProps} />;

This is because the current version of React Router provides RoutingContext which has since been renamed to RouterContext (which is what the docs talk about).

That’s about it! Let me know your thoughts and suggestions!