web-dev-qa-db-ja.com

反応、反応ルーター、およびエクスプレスを使用したサーバー側レンダリング

私は私のreactアプリのサーバーサイドレンダリングを設定しようとしていますが、素晴らしい react-router モジュールを使用して、非jsの状況(一部のクローラー、ユーザーは何らかの理由でjsをオフにしました)。しかし、私は問題に直面しています。私はここで素晴らしい応答を使用していました https://stackoverflow.com/a/28558545/3314701 種類のガイドとして、しかし奇妙なエラーが私に投げられています。 react.renderToString()を使用しようとすると、永続的なSyntax Errorを取得します。サーバー側のレンダリングを誤って設定しているか、明らかなものが欠落しているか、または他に何かがありますか?

私のセットアップ:

本当に基本的なExpressサーバー

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

トップレベルのリアクション<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;

出力:

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

だから、私は自分でこれを解決することになりました。私が得ていたエラーは、レンダリングされていないネストされたコンポーネントからのものでした。そのため、jsエンジンはランダムな_<_ charについて文句を言っていました。

そして今、私の急行のセットアップに。サーバー側レンダリングでreactをどのように使用できるかを知らない人にとっては、かなり簡単です:Nodeまたはio.jsを使用してReactのrenderToString()を呼び出すことができますメソッドをコンポーネントに送信してから、それを要求元のクライアントに送信します。おそらく、このアプローチがもたらすメリットは聞いたことがあるでしょう。

  1. googleが既にクローラーでJSを実行できるとしても、SEOの使いやすさは向上します。これはかなり安全な賭けです
  2. 非js状況のフォールバック。アプリのスクリプトの読み込みが遅い場合でも、実際のページをクライアントにレンダリングすることができ、空白の画面を見つめながら待機させることはできません。これにより、ブラウザでJSが無効になっているユーザーも、ほとんどの場合アプリと対話できます。リンクは引き続き機能し、フォームは引き続き送信できます、&c。
  3. クライアントとサーバー間のコード共有の追加の利点を得ることができます。複雑さが減るという事実を除けば、これに関して必ずしも驚くべきことは何もありません。そのため、複雑さを減らすことで得られるメリットをすべて享受できます(カップリングの可能性が減り、保守が容易になり、構造がよりシンプルになり、同型性などが得られます)。
  4. さらなる副次的な利点は、他の方法で使用しなければならない面倒なハッシュフラグメントの代わりに、react-routerのhtml5履歴APIを使用できることです。

このアプローチに夢中になり、ロード中にアプリのプレースホルダーなどを処理したり、ロードの遅い状態(ロード中のFacebookなど)に他のフィードバックメカニズムを提供したりすることもできます。

基本的なアプローチは、おおよそ次のように動作します。

  1. ブートストラップ時に、ノードアプリは_routes.jsx_に基づいて反応ルーターインスタンスをインスタンス化します
  2. 要求はサーバーに送られ、サーバーはexpress '_req.path_を使用して、反応するルーターが処理するルート文字列を提供します。
  3. 次に、Reactルーターは、指定されたルートを照合し、対応するコンポーネントをレンダリングして、Expressが送り返すようにします。
  4. ReactはHTML応答を送信し、クライアントはアプリスクリプトの速度に関係なく何かをペイントします。優れたCDNを介してサービスを提供しますが、最適な配信と圧縮を使用した場合でも、低速ネットワークでは一時的に空白の画面が残ります。
  5. 必要なアプリスクリプトを読み込んだ後、Reactは同じ_routes.jsx_ファイルを使用して、これから_react-router_でhtmlを引き継いで生成できます。ここでのもう1つの利点は、アプリのコードをキャッシュすることができ、将来のインタラクションが別の呼び出しに依存する必要さえないことを願っています。

注目に値するもう1つのポイント:webpackを使用して反応コードをバンドルすると、_browser.jsx_がエントリポイントになります。サーバー側レンダリングのリファクタリング前は、以前は_app.jsx_でした。どこでレンダリングされるものに対応するために、構造を再構成する必要があるかもしれません。 :)

コード:

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(エクスプレスサーバー)

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

便利なリンク:

40
markthethomas