使用 react、react-router 和 express 进行服务器端渲染
Server side rendering with react, react-router, and express
我正在尝试为我的 React 应用程序设置服务器端渲染,我正在尝试使用很棒的 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 作为各种指南,但我收到了奇怪的错误.我在尝试使用 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});
});
});
顶级 React <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)
所以,我最终自己解决了这个问题。我得到的错误来自未渲染的嵌套组件,这就是为什么 js 引擎抱怨随机 <
字符。
现在开始我的快速设置。对于那些不知道如何将 React 用于服务器端渲染的人来说,它非常简单:Node 或 io.js 可用于在组件上调用 React 的 renderToString()
方法,然后将其发送到请求的客户端。您可能已经听说过这种方法带来的好处,但对于那些不知道的人:
- 即使 google 已经可以在它的爬虫中执行 JS,你也会获得更多的 SEO 友好性;这几乎只是一个更安全的赌注
- 非 js 情况的回退。如果您的应用程序脚本加载缓慢,您仍然可以将实际页面呈现给您的客户,而不是让他们盯着空白屏幕等待。这也允许在浏览器上禁用 JS 的人在大多数情况下仍能与您的应用程序进行交互;链接仍然有效,表单仍然可以提交,&c.
- 您可以获得客户端和服务器之间代码共享的额外好处。除了复杂性降低这一事实之外,这没有什么必然令人难以置信的,因此,您可以获得复杂性降低的所有好处(可能减少耦合、更容易维护、结构更简单、同构性等)
- 另一个附带的好处是能够使用 react-router 的 html5 历史记录 API,而不是你必须使用的烦人的哈希片段。
您甚至可以对这种方法感到疯狂,并在应用程序加载时处理诸如占位符之类的事情,或者为缓慢加载状态提供其他反馈机制(加载时类似于 Facebook)。
基本的操作方式大致如下:
- 在bootstrap时,节点应用实例化了一个基于
routes.jsx
的react-router实例
- 请求到达服务器,然后服务器使用 express'
req.path
提供路由字符串供 react-router 处理。
- React router 然后匹配提供的路由并尝试渲染相应的组件以供 express 发回。
- React 发送 html 响应,无论您的应用程序脚本的速度如何,您的客户都可以绘制一些东西。我们通过一个很棒的 CDN 为我们提供服务,但即使有最好的分发和压缩,慢速网络仍然会给人们留下一个暂时的空白屏幕。
- 加载所需的应用程序脚本后,React 可以使用相同的
routes.jsx
文件接管并从现在开始生成 html 和 react-router
。这里的另一个好处是您的应用程序代码可以被缓存,并且希望未来的交互甚至不必依赖另一个调用。
还有一点值得注意:我使用 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>
有用的链接:
我正在尝试为我的 React 应用程序设置服务器端渲染,我正在尝试使用很棒的 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 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});
});
});
顶级 React <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)
所以,我最终自己解决了这个问题。我得到的错误来自未渲染的嵌套组件,这就是为什么 js 引擎抱怨随机 <
字符。
现在开始我的快速设置。对于那些不知道如何将 React 用于服务器端渲染的人来说,它非常简单:Node 或 io.js 可用于在组件上调用 React 的 renderToString()
方法,然后将其发送到请求的客户端。您可能已经听说过这种方法带来的好处,但对于那些不知道的人:
- 即使 google 已经可以在它的爬虫中执行 JS,你也会获得更多的 SEO 友好性;这几乎只是一个更安全的赌注
- 非 js 情况的回退。如果您的应用程序脚本加载缓慢,您仍然可以将实际页面呈现给您的客户,而不是让他们盯着空白屏幕等待。这也允许在浏览器上禁用 JS 的人在大多数情况下仍能与您的应用程序进行交互;链接仍然有效,表单仍然可以提交,&c.
- 您可以获得客户端和服务器之间代码共享的额外好处。除了复杂性降低这一事实之外,这没有什么必然令人难以置信的,因此,您可以获得复杂性降低的所有好处(可能减少耦合、更容易维护、结构更简单、同构性等)
- 另一个附带的好处是能够使用 react-router 的 html5 历史记录 API,而不是你必须使用的烦人的哈希片段。
您甚至可以对这种方法感到疯狂,并在应用程序加载时处理诸如占位符之类的事情,或者为缓慢加载状态提供其他反馈机制(加载时类似于 Facebook)。
基本的操作方式大致如下:
- 在bootstrap时,节点应用实例化了一个基于
routes.jsx
的react-router实例
- 请求到达服务器,然后服务器使用 express'
req.path
提供路由字符串供 react-router 处理。 - React router 然后匹配提供的路由并尝试渲染相应的组件以供 express 发回。
- React 发送 html 响应,无论您的应用程序脚本的速度如何,您的客户都可以绘制一些东西。我们通过一个很棒的 CDN 为我们提供服务,但即使有最好的分发和压缩,慢速网络仍然会给人们留下一个暂时的空白屏幕。
- 加载所需的应用程序脚本后,React 可以使用相同的
routes.jsx
文件接管并从现在开始生成 html 和react-router
。这里的另一个好处是您的应用程序代码可以被缓存,并且希望未来的交互甚至不必依赖另一个调用。
还有一点值得注意:我使用 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>
有用的链接: