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:
- Your Koa app uses React Router’s match()
- match() takes your routes and the url, and a callback
- 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 renderToString function.
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
};
var reactString; 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) {
reactString = ReactDOMServer.renderToString(RouterContext(renderProps));
} else {
this.throw('Not Found', 404);
}
});
yield this.render('layout', {react: reactString});});var port = process.env.PORT || 3000;
app.listen(port);
});
A couple of things thrown in there:
- I’m using Babel’s require hook to support the jsx in my React component
- This doesn’t transpile the file it’s in, so I’m using plain routes
- This is also why I’ve placed <RouterContext /> in a separate file
- 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).