I'm trying to set up server-side rendering for my react app and I'm trying to use the great react-router module to allow it to handle non-js situations (some crawlers, when a user had js turned off for some reason). However, I'm running into trouble. I've been using the great response here https://stackoverflow.com/a/28558545/3314701 as a guide of sorts, but I'm getting strange errors thrown at me. I get a persistent Syntax Error
when trying to use react.renderToString()
. Am I setting up the server-side rendering incorrectly, missing something obvious, or anything else?
My setup:
Really basic Express server
require('babel/register'); var app = express(); // misc. express config... var Router = require('react-router'), routes = require('../jsx/app').routes, React = require('react'); app.use(function(req, res, next) { var router = Router.create({location: req.url, routes: routes}); router.run(function(Handler, state) { console.log(Handler); var html = React.renderToString(<Handler/>); return res.render('react_page', {html: html}); }); });
Top-level react <App/>
component
// Shims require('intl'); require('es5-shim'); var React = require('react/addons'), Router = require('react-router'), Nav = require('./nav'), injectTapEventPlugin = require("react-tap-event-plugin"), window.React = React; // export for http://fb.me/react-devtools // Intl var ReactIntl = require('react-intl'), IntlMixin = ReactIntl.IntlMixin; var Route = Router.Route, DefaultRoute = Router.DefaultRoute, NotFoundRoute = Router.NotFoundRoute, RouteHandler = Router.RouteHandler; var App = React.createClass({ mixins: [IntlMixin], getInitialState: function() { return { connected: false, loaded: false, user: true }; }, render: function() { return ( <div className="container-fluid"> <Nav/> <RouteHandler/> <Footer/> </div> ); } }); var routes = ( <Route name="Home" path="/" handler={App}> <DefaultRoute name="Welcome " handler={Welcome}/> <Route name="Bar" path="/bar" handler={Bar}> <Route name="foo" path="/foo" handler={Foo}></Route> </Route> ); Router.run(routes, Router.HistoryLocation , function(Handler) { React.render(<Handler/>, document.getElementById('app')); }); module.routes = routes;
output:
flo-0,1,2 (err): <div className="progressbar-container" > flo-0,1,2 (err): ^ flo-0,1,2 (err): SyntaxError: Unexpected token < flo-0,1,2 (err): at exports.runInThisContext (vm.js:73:16) flo-0,1,2 (err): at Module._compile (module.js:443:25) flo-0,1,2 (err): at Module._extensions..js (module.js:478:10) flo-0,1,2 (err): at Object.require.extensions.(anonymous function) [as .js] (/Users/user/Code/foobar/apps/flo/node_modules/babel/node_modules/babel-core/lib/babel/api/register/node.js:161:7) flo-0,1,2 (err): at Module.load (module.js:355:32) flo-0,1,2 (err): at Function.Module._load (module.js:310:12) flo-0,1,2 (err): at Function.<anonymous> (/Users/user/.nvm/versions/node/v0.12.4/lib/node_modules/pm2/node_modules/pmx/lib/transaction.js:62:21) flo-0,1,2 (err): at Function.cls_wrapMethod (/Users/user/Code/foobar/apps/bar/node_modules/newrelic/lib/shimmer.js:230:38) flo-0,1,2 (err): at Function.<anonymous> (/Users/user/Code/foobar/apps/bar/node_modules/pmx/lib/transaction.js:62:21) flo-0,1,2 (err): at Module.require (module.js:365:17) flo-0,1,2 (err): at require (module.js:384:17)
The most basic server rendering in React Router is pretty straightforward. However, there's a lot more to consider than just getting the right routes to render.
Client-side routing in React helps to maintain the seamless user experience that a typical single-page application promises. This is achieved through an external React library called React Router.
Render React app using Express.js: Now, open server/index. js and add an import for path and another app. use to serve the build folder that will be created by create-react-app soon. const path = require("path");app.
Server-side rendering (SSR) is a popular technique for rendering a normally client-side only single page app (SPA) on the server and then sending a fully rendered page to the client. The client's JavaScript bundle can then take over and the SPA can operate as normal.
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:
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:
routes.jsx
req.path
to provide a route string for react-router to handle.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 configuration const 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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With