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

Я надеюсь, что это помогает.

Другие вопросы по тегам