Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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) 
like image 924
markthethomas Avatar asked Jun 03 '15 17:06

markthethomas


People also ask

Is React Router server-side rendering?

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.

Is React Router client-side or server-side?

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.

How do I render a React component from Express?

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.

What is server-side rendering in Express?

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.


Video Answer


1 Answers

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 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:

  • https://ifelse.io/2015/08/27/server-side-rendering-with-react-and-react-router/
  • https://github.com/rackt/react-router
like image 154
markthethomas Avatar answered Sep 19 '22 13:09

markthethomas