最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - server rendering with react-router v4 and express.js - Stack Overflow

programmeradmin1浏览0评论

I'm trying to set up server-side rendering with the newest version of react-router v.4. I followed this tutorial .

I get following error when I refresh browser: Invariant Violation: React.Children.only expected to receive a single React element child.

my routes.jsx file:

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

and in index.jsx I'm rendering app

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

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

Now as server I'm using express.js. Here is my configuration:

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();
  }
});

I didn't find any tutorial for server-rendering with this version of react-routes except official. Can anyone help me what I'm doing wrong ? thanks.

I'm trying to set up server-side rendering with the newest version of react-router v.4. I followed this tutorial https://react-router.now.sh/ServerRouter.

I get following error when I refresh browser: Invariant Violation: React.Children.only expected to receive a single React element child.

my routes.jsx file:

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

and in index.jsx I'm rendering app

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

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

Now as server I'm using express.js. Here is my configuration:

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();
  }
});

I didn't find any tutorial for server-rendering with this version of react-routes except official. Can anyone help me what I'm doing wrong ? thanks.

Share Improve this question edited Jan 5, 2017 at 15:52 Morty asked Jan 5, 2017 at 13:52 MortyMorty 1633 silver badges20 bronze badges
Add a ment  | 

5 Answers 5

Reset to default 3

Solved !

First problem was that I had spaces around <Routes /> tag.

Correct solution:

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

Second problem was in included <Header /> tag in routes.jsx file.

I had the following error (Invariant Violation: Element type is invalid: expected a string (for built-in ponents) or a class/function (for posite ponents) but got: undefined. Check the render method of StatelessComponent)

File Header.jsx contained the following line of code:

import Link  from 'react-router';

Correct solution: (I forgot to put curly brackets ):

 import { Link } from 'react-router';

The big issue is that the<BrowserRouter> is only expected to have one child, so you should wrap it's children in a div. This is done so that React Router is environment agnostic (you can't render a div in React Native, so RR expects you to include the appropriate wrapper).

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

As a secondary issue, you are including the <BrowserRouter> in your <App> ponent, so it will be rendered on the server. You do not want this. Only the <ServerRouter> should be rendered on the server. You should move the <BrowserRouter> further up your client side ponent hierarchy to avoid this.

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

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

because BrowserRouter doesn't exist on react-router, try install and import it from react-router-dom

I believe the answers listed above is outdated. As of today, the official react-router docs suggest using StaticRouter instead of ServerRouter for server side rendered apps.

A fantastic documentation can be found here.

For anybody ing later, Ryan Florence has added a snippet of how to acplish this.

SSR in React Router v4

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

// ponents
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
  }

  ponentDidMount() {
    // 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, ponent, 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.ponent.fetchData ?
          route.ponent.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')
)
发布评论

评论列表(0)

  1. 暂无评论