Server Rendering With React-router V4 And Express.js
Solution 1:
Solved !
First problem was that I had spaces around <Routes />
tag.
Correct solution:
<ServerRouterlocation={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 components) or a class/function (for composite components) but got: undefined. Check the render method of StatelessComponent
)
File Header.jsx contained the following line of code:
importLinkfrom'react-router';
Correct solution: (I forgot to put curly brackets ):
import { Link } from'react-router';
Solution 2:
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).
exportdefault () =>
<BrowserRouter><div><Header /><Matchpattern="/"component={Home} /><Matchpattern="/about"component={About} /><Misscomponent={NotFound} /></div></BrowserRouter>;
As a secondary issue, you are including the <BrowserRouter>
in your <App>
component, 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 component hierarchy to avoid this.
// Appexportdefault () =>
<div><Header /><Matchpattern="/"component={Home} /><Matchpattern="/about"component={About} /><Misscomponent={NotFound} /></div>;
// index.jsrender((
<BrowserRouter><App /></BrowserRouter>
), document.getElementById('app'))
Solution 3:
because BrowserRouter
doesn't exist on react-router
, try install and import it from react-router-dom
Solution 4:
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.
Solution 5:
For anybody coming later, Ryan Florence has added a snippet of how to accomplish this.
// routes.jsconst routes = [
{
path: '/',
component: Home,
exact: true
},
{
path: '/gists',
component: Gists
},
{
path: '/settings',
component: Settings
}
]
// componentsclassHomeextendsReact.Component {
// called in the server render, or in cDMstaticfetchData(match) {
// going to want `match` in here for params, etc.returnfetch(/*...*/)
}
state = {
// if this is rendered initially we get data from the server renderdata: 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 fetchif (!this.state.data) {
this.constructor.fetchData(this.props.match).then(data => {
this.setState({ data })
})
}
}
// ...
}
// App.jsconstApp = ({ 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.jsimport { 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 configconst 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(
<StaticRoutercontext={context}location={req.url}><Approutes={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.jsrender(
<Approutes={routes}initialData={window.DATA} />,
document.getElementById('root')
)
Post a Comment for "Server Rendering With React-router V4 And Express.js"