ReactJS + ServerSide с ExpressJS - нужен способ диктовать, что загружать на сервер против клиента
Поэтому я работаю над POC-приложением, которое должно иметь оптимизированный для SEO контент (=== на сервере). Но на одном и том же маршруте могут быть динамические данные, поступающие из разных источников, и может потребоваться некоторое время, чтобы собрать все эти данные. Эти динамические данные не нужны для SEO, поэтому будет реализована асинхронная загрузка.
То, что я пока имею, выглядит примерно так (имейте в виду, что это всего лишь образец):
// routes.js
import serverRender from './services/aboutService';
const routes = (
<Route path="/" component={Layout}>
<IndexRoute component={Home}/>
<Route path="/about">
<Route
path="/About"
component={AboutContainer}
serverRender={serverRender} />
</Route>
</Route>
);
// aboutService.js
const serverRender = () => {
return axios.get("http://localhost:3002/services/about")
.then(res => res.data)
.catch(function (error) {
console.log(error);
});
}
export default serverRender;
// AboutContainer.js
import React from 'react';
export default class About extends Component {
constructor(props){
super(props);
this.state = {
data: { username: "", isFetching : true, hasError: false }
};
this._fetchNonSEOData = this.fetchNonSEOData.bind(this);
console.log("CompetitionContainer constructor: ", props)
}
componentDidMount(){
//this._fetchNonSEOData();
}
_fetchNonSEOData(){
// dynamic content, not important for Crawlers
// will call setState here to update state properties
}
render(){
return (
<div>
<h1>User: {this.props.data.username}</h1>
<div className="non-seo">
{this._fetchNonSEOData()}
</div>
</div>
)
}
}
server.js, который использует ExpressJS (небольшой кусочек):
// server.js app.get ('*', (req, res) => {ReactRouter.match ({маршруты, местоположение: req.url }, (err, redirectLocation, renderProps) => {
if (err) {
return res.status(500).send(err.message);
}
if (redirectLocation) {
return res.redirect(302, redirectLocation.pathname + redirectLocation.search);
}
let markup;
if (renderProps) {
preRenderMiddleware(renderProps)
.then(data => {
const appElement = (
<ReactRouter.RouterContext
{...renderProps}
createElement={(Component, props) => {
return <Component data={data} {...props} />;
}}
/>
);
//
let markup = ReactDOMServer.renderToString(appElement);
console.log("AppString:", markup)
return res.render('index', { markup, head });
})
} else {
// otherwise we can render a 404 page
markup = ReactDOMServer.renderToString(<NotFoundPage/>);
res.status(404);
}
});
});
Итак, как вы видите, есть preRenderMiddleware, который возвращает разрешенный Promise, и я думаю, что в этой части я могу ошибаться:
//preRenderMiddleware.js
const defaultServerRender = () => Promise.resolve();
function preRenderMiddleware({ routes, location, params }) {
const matchedRoute = routes[routes.length - 1];
const serverRenderHandler = matchedRoute.serverRender || defaultServerRender;
return serverRenderHandler(params);
}
Так что моя проблема в том, что получение ожидаемой разметки с сервера - в AboutContainer this.props.data получает данные. Но затем, когда запускается клиентская сторона, this.props.data не определено.
Кто-нибудь может указать мне какое-нибудь хорошее решение? Я уверен, что я не первый человек с этой проблемой. Я наткнулся на некоторые решения для этого, но все повторно запрашиваемые данные на клиенте, чего я хотел бы избежать, и это снова будет немного странным.
Также я не хочу использовать Redux / любой Flux для этого.
Какие-нибудь мысли?
1 ответ
Проблема в том, что у вас есть реквизиты данных на стороне сервера, а не на стороне клиента. Вы должны передать его на стороне клиента. Я предлагаю вам попробовать следующее решение:
На стороне сервера, в вашем примере компонента AboutContainer, запишите реквизиты в скрытый элемент div и прочитайте его на стороне клиента:
var canUseDOM = !!(
(typeof window !== 'undefined' &&
window.document && window.document.createElement)
);
export default class About extends Component {
render(){
initData(){
if(!canUseDOM)return <div hidden id="propsData">{this.props.data}</div>
}
return (
<div>
<h1>User: {this.props.data.username}</h1>
<div className="non-seo">
{this._fetchNonSEOData()}
</div>
{initData()}
</div>
)
}
}
А теперь на вашей стороне клиента:
const data = document.getElementById("namee").innerHTML
Я надеюсь, что это помогает.