使用 react、react-router 和 express 进行服务器端渲染 [英] Server side rendering with react, react-router, and express

查看:31
本文介绍了使用 react、react-router 和 express 进行服务器端渲染的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试为我的 React 应用程序设置服务器端渲染,并且我正在尝试使用出色的 react-router 模块允许它处理非 js 情况(一些爬虫,当用户出于某种原因关闭 js 时).但是,我遇到了麻烦.我一直在使用这里的精彩回应 https://stackoverflow.com/a/28558545/3314701 作为指南排序,但我收到了奇怪的错误.尝试使用 react.renderToString() 时,我得到一个持久的 Syntax Error.我是否错误地设置了服务器端渲染,遗漏了一些明显的东西,还是其他任何东西?

我的设置:

真正基本的 Express 服务器

require('babel/register');var app = express();//杂项.快速配置...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});路由器运行(函数(处理程序,状态){控制台日志(处理程序);var html = React.renderToString();return res.render('react_page', {html: html});});});

顶级 react 组件

//垫片要求('国际');要求('es5-shim');var React = require('react/addons'),Router = require('react-router'),Nav = require('./nav'),injectTapEventPlugin = require("react-tap-event-plugin"),window.React = 反应;//导出为 http://fb.me/react-devtools//国际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({混合:[IntlMixin],getInitialState:函数(){返回 {连接:假,加载:假,用户:真的};},渲染:函数(){返回 (<div className="容器流体"><导航/><RouteHandler/><页脚/>

);}});无功路线 = (<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></路线>);Router.run(routes, Router.HistoryLocation, function(Handler) {React.render(, document.getElementById('app'));});module.routes = 路线;

输出:

flo-0,1,2 (err): <div className="progressbar-container" >flo-0,1,2(错误):^flo-0,1,2 (err): SyntaxError: Unexpected token <flo-0,1,2(错误):在exports.runInThisContext (vm.js:73:16)flo-0,1,2 (err): 在 Module._compile (module.js:443:25)flo-0,1,2 (err): 在 Module._extensions..js (module.js:478:10)flo-0,1,2 (err): 在 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): 在 Module.load (module.js:355:32)flo-0,1,2 (err): 在 Function.Module._load (module.js:310:12)flo-0,1,2(错误):在函数处.<匿名>(/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): 在 Function.cls_wrapMethod (/Users/user/Code/foobar/apps/bar/node_modules/newrelic/lib/shimmer.js:230:38)flo-0,1,2(错误):在函数处.<匿名>(/Users/user/Code/foobar/apps/bar/node_modules/pmx/lib/transaction.js:62:21)flo-0,1,2 (err): 在 Module.require (module.js:365:17)flo-0,1,2 (err): at require (module.js:384:17)

解决方案

所以,我最终自己解决了这个问题.我得到的错误来自未渲染的嵌套组件,这就是 js 引擎抱怨随机 < 字符的原因.

现在是我的快速设置.对于那些不知道如何将 react 与服务器端渲染一起使用的人来说,这相当简单:Node 或 io.js 可用于在组件上调用 React 的 renderToString() 方法,然后将其发送给请求客户端.您可能已经听说过这种方法带来的好处,但对于那些不知道的人:

  1. 您获得了更多的 SEO 友好性,即使谷歌已经可以在它的爬虫中执行 JS;这几乎只是一个更安全的赌注
  2. 非 js 情况的回退.如果您的应用程序脚本加载缓慢,您仍然可以将实际页面呈现给您的客户端,而不会让他们在盯着空白屏幕时等待.这也允许在浏览器上禁用 JS 的人在大多数情况下仍然与您的应用程序交互;链接仍然有效,表单仍然可以提交,&c.
  3. 您可以获得客户端和服务器之间代码共享的额外好处.除了降低复杂性这一事实之外,没有什么必然令人难以置信的,因此,您可以获得降低复杂性的所有好处(可能减少耦合,更容易维护,结构更简单,同构性,&c.)
  4. 另一个附带好处是能够使用 react-router 的 html5 历史 API,而不是您必须以其他方式使用的烦人的哈希片段内容.

您甚至可以对这种方法感到疯狂,并在加载时处理应用占位符之类的事情,或者为缓慢加载状态(加载时类似于 Facebook)提供其他反馈机制.

基本方法大致按以下方式运行:

  1. 在引导时,节点应用程序根据 routes.jsx
  2. 实例化一个 react-router 实例
  3. 请求到达服务器,然后服务器使用 express' req.path 为 react-router 提供路由字符串来处理.
  4. React 路由器然后匹配提供的路由,并尝试渲染相应的组件以供 express 发回.
  5. React 发送 html 响应,无论您的应用程序脚本的速度如何,您的客户端都可以绘制一些东西.我们通过出色的 CDN 为我们提供服务,但即使有最好的分发和压缩,慢速网络仍然会让人们暂时处于空白屏幕.
  6. 加载所需的应用程序脚本后,React 可以使用相同的 routes.jsx 文件来接管并生成带有 react-router 的 html.这里的另一个好处是您的应用程序代码可以被缓存,未来的交互有望甚至不必依赖另一个调用.

还有一点值得注意:我使用 webpack 来打包我的 React 代码,现在 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(快速服务器):

//...其他快速配置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);比赛({路线:路线,地点:地点,}, (err, redirectLocation, renderProps) =>{如果(重定向位置){res.redirect(301, redirectLocation.pathname + redirectLocation.search);}否则如果(错误){控制台日志(错误);下一个(错误);//res.send(500, error.message);} else if (renderProps === null) {资源状态(404).send('未找到');} 别的 {res.send('<!DOCTYPE html>' + React.renderToString(<RoutingContext {...renderProps}/>));}});});//...其他快递配置

Routes.jsx

<DefaultRoute 组件={欢迎}/><Route path="dashboard" component={Dashboard}/><路由路径=登录"组件={登录}/></路线>

App.jsx

<头><link rel="stylesheet" href="/assets/styles/app.css"/><身体><导航/><RouteHandler/><页脚/><身体/>

有用的链接:

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)

解决方案

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:

这篇关于使用 react、react-router 和 express 进行服务器端渲染的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆