使用react-router v4和express.js进行服务器渲染

时间:2017-01-05 13:52:13

标签: javascript node.js reactjs express react-router

我正在尝试使用最新版本的react-router v.4设置服务器端渲染。我按照本教程https://react-router.now.sh/ServerRouter进行了操作。

刷新浏览器时出现以下错误:不变违规:React.Children.only预计会收到一个React元素子项。

我的 routes.jsx 文件:

export default () =>
  <div>
    <Header />
    <Match pattern="/" component={Home} />
    <Match pattern="/about" component={About} />
    <Miss component={NotFound} />
  </div>;

并在 index.jsx 我正在呈现应用

import  BrowserRouter from 'react-router';    
import Routes from './routes';

ReactDOM.render(<BrowserRouter> <Routes /> </BrowserRouter>, document.getElementById('app'));

现在作为服务器,我正在使用 express.js 。这是我的配置:

import  Routes  from '../routes';

server.use((req, res) => {
  const context = createServerRenderContext();
  let markup = renderToString(
    <ServerRouter location={req.url} context={context} > <Routes /> </ServerRouter>);
  const result = context.getResult();

  if (result.redirect) {
    res.writeHead(301, {
      Location: result.redirect.pathname,
    });
    res.end();
  } else {
    if (result.missed) {
      res.writeHead(404);
      markup = renderToString(
        <ServerRouter location={req.url} context={context}> <Routes /> </ServerRouter>);
    }
    res.write(markup);
    res.end();
  }
});

除了官方版本之外,我没有找到任何使用此版本的react-routes进行服务器渲染的教程。 谁能帮助我,我做错了什么?谢谢。

5 个答案:

答案 0 :(得分:3)

解决了!

第一个问题是<Routes />标记周围有空格。

正确的解决方案:

<ServerRouter location={req.url} context={context}><Routes /></ServerRouter>);

第二个问题是在routes.jsx文件中包含<Header />标记。

我遇到以下错误(不变违规:元素类型无效:期望字符串(用于内置组件)或类/函数(用于复合组件)但得到:undefined。检查render方法StatelessComponent

File Header.jsx包含以下代码行:

import Link  from 'react-router';

正确的解决方案:(我忘了放大括号):

 import { Link } from 'react-router';

答案 1 :(得分:1)

最大的问题是<BrowserRouter>只有一个孩子,所以你应该将它包裹在div中。这样做是为了使React Router与环境无关(您无法在React Native中呈现div,因此RR希望您包含相应的包装器。)

export default () =>
  <BrowserRouter>
    <div>
      <Header />
      <Match pattern="/" component={Home} />
      <Match pattern="/about" component={About} />
      <Miss component={NotFound} />
    </div>
  </BrowserRouter>;

作为次要问题,您在<BrowserRouter>组件中包含<App>,因此它将在服务器上呈现。你不想要这个。只应在服务器上呈现<ServerRouter>。您应该将<BrowserRouter>进一步向上移动客户端组件层次结构以避免这种情况。

// App
export default () =>
  <div>
    <Header />
    <Match pattern="/" component={Home} />
    <Match pattern="/about" component={About} />
    <Miss component={NotFound} />
  </div>;

// index.js
render((
  <BrowserRouter>
    <App />
  </BrowserRouter>
), document.getElementById('app'))

答案 2 :(得分:1)

BrowserRouterreact-router不存在,请尝试安装并从react-router-dom

导入

答案 3 :(得分:0)

我相信上面列出的答案已经过时了。截至今天,官方 react-router 文档建议使用StaticRouter代替 ServerRouter 作为服务器端呈现的应用。

可以找到精彩的文档here.

答案 4 :(得分:-1)

对于任何后来的人来说,Ryan Florence已经添加了一个如何实现这一目标的片段。

SSR in React Router v4

// routes.js
const routes = [
  {
    path: '/',
    component: Home,
    exact: true
  },
  {
    path: '/gists',
    component: Gists
  },
  {
    path: '/settings',
    component: Settings
  }
]

// components
class Home extends React.Component {

  // called in the server render, or in cDM
  static fetchData(match) {
    // going to want `match` in here for params, etc.
    return fetch(/*...*/)
  }

  state = {
    // if this is rendered initially we get data from the server render
    data: this.props.initialData || null
  }

  componentDidMount() {
    // if rendered initially, we already have data from the server
    // but when navigated to in the client, we need to fetch
    if (!this.state.data) {
      this.constructor.fetchData(this.props.match).then(data => {
        this.setState({ data })
      })
    }
  }

  // ...
}

// App.js
const App = ({ routes, initialData = [] }) => (
  <div>
    {routes.map((route, index) => (
      // pass in the initialData from the server for this specific route
      <Route {...route} initialData={initialData[index]} />
    ))}
  </div>
)

// server.js
import { matchPath } from 'react-router'

handleRequest((req, res) => {
  // we'd probably want some recursion here so our routes could have
  // child routes like `{ path, component, routes: [ { route, route } ] }`
  // and then reduce to the entire branch of matched routes, but for
  // illustrative purposes, sticking to a flat route config
  const matches = routes.reduce((matches, route) => {
    const match = matchPath(req.url, route.path, route)
    if (match) {
      matches.push({
        route,
        match,
        promise: route.component.fetchData ?
          route.component.fetchData(match) : Promise.resolve(null)
      })
    }
    return matches
  }, [])

  if (matches.length === 0) {
    res.status(404)
  }

  const promises = matches.map((match) => match.promise)

  Promise.all(promises).then((...data) => {
    const context = {}
    const markup = renderToString(
      <StaticRouter context={context} location={req.url}>
        <App routes={routes} initialData={data}/>
      </StaticRouter>
    )

    if (context.url) {
      res.redirect(context.url)
    } else {
      res.send(`
        <!doctype html>
        <html>
          <div id="root">${markup}</div>
          <script>DATA = ${escapeBadStuff(JSON.stringify(data))}</script>
        </html>
      `)
    }
  }, (error) => {
    handleError(res, error)
  })
})

// client.js
render(
  <App routes={routes} initialData={window.DATA} />,
  document.getElementById('root')
)