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 badges5 Answers
Reset to default 3Solved !
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')
)