web-dev-qa-db-fra.com

Rendu côté serveur avec react, react-router et express

J'essaie de configurer le rendu côté serveur pour mon application React et j'essaie d'utiliser le grand module react-router pour lui permettre de gérer des situations non js (certains robots, lorsqu'un l'utilisateur avait désactivé js pour une raison quelconque). Cependant, j'ai des ennuis. J'ai utilisé la grande réponse ici https://stackoverflow.com/a/28558545/3314701 comme guide en quelque sorte, mais je reçois des erreurs étranges. J'obtiens un Syntax Error Persistant lorsque j'essaie d'utiliser react.renderToString(). Suis-je en train de configurer le rendu côté serveur de manière incorrecte, manquant quelque chose d’évident ou autre chose?

Ma configuration:

Serveur Express vraiment basique

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});
  });
});

Composant de réaction de niveau supérieur <App/>

// 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;

production:

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)
30
markthethomas

J'ai donc fini par résoudre celui-ci moi-même. L'erreur que j'obtenais venait d'un composant imbriqué non rendu, c'est pourquoi le moteur js se plaignait d'un caractère aléatoire <.

Et maintenant à ma configuration express. Pour ceux qui ne savent pas comment réagir peut être utilisé avec le rendu côté serveur, c'est assez simple: Node ou io.js peut être utilisé pour appeler la renderToString() de React sur un composant, puis l'envoyer au client demandeur. Vous avez probablement déjà entendu les avantages de cette approche, mais pour ceux qui ne le savent pas:

  1. vous obtenez plus de convivialité SEO, même si google peut déjà exécuter JS dans ses robots; c'est à peu près juste un pari plus sûr
  2. Remplacement pour les situations non js. Si le script de votre application se charge lentement, vous pouvez toujours rendre la page réelle à votre client et ne pas le faire attendre tout en regardant un écran vide. Cela permet également à une personne avec JS désactivé sur son navigateur d'interagir avec votre application pour la plupart; les liens fonctionneront toujours, les formulaires pourront toujours être envoyés, etc.
  3. Vous pouvez bénéficier des avantages supplémentaires du partage de code entre le client et le serveur. Il n'y a rien de nécessairement incroyable à part cela, à part le fait que la complexité est diminuée et, en tant que telle, vous obtenez tous les avantages d'une complexité réduite (potentiellement moins de couplage, une maintenabilité plus facile, une plus grande simplicité de structure, une isomorphicité, etc.)
  4. Un autre avantage secondaire est la possibilité d'utiliser l'API d'historique html5 de react-router au lieu des trucs de fragments de hachage ennuyeux que vous devez autrement utiliser.

Vous pouvez même devenir fou avec cette approche et gérer des choses comme des espaces réservés pour votre application pendant qu'elle se charge ou fournir d'autres mécanismes de rétroaction pour un état de chargement lent (à la Facebook pendant le chargement).

L'approche de base fonctionne grosso modo de la manière suivante:

  1. Au démarrage, l'application de nœud instancie une instance de routeur de réaction basée sur routes.jsx
  2. La requête est envoyée au serveur, qui utilise ensuite express 'req.path Pour fournir une chaîne de routage à gérer par react-router.
  3. Le routeur React correspond alors à la route fournie et essaie de restituer le composant correspondant pour que express le renvoie.
  4. React envoie la réponse html et votre client peut peindre quelque chose quelle que soit la vitesse du script de votre application. Nous servons le nôtre sur un excellent CDN, mais même avec la meilleure distribution et compression, les réseaux lents laisseraient encore les gens avec un écran temporairement vide.
  5. Après avoir chargé le script d'application nécessaire, React peut utiliser le même fichier routes.jsx Pour prendre le relais et générer du HTML avec react-router À partir de maintenant. Un autre avantage ici est que le code de votre application peut être mis en cache et que les interactions futures, espérons-le, n'auront même pas besoin de s'appuyer sur un autre appel.

Un autre point à noter: j'utilise webpack pour regrouper mon code de réaction et maintenant browser.jsx Est le point d'entrée. Avant de refactoriser le rendu côté serveur, il était auparavant app.jsx; vous devrez peut-être reconfigurer votre structure pour tenir compte de ce qui est rendu où. :)

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 (serveur express) :

//...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>

liens utiles:

40
markthethomas