I am having trouble rendering an isomorphic app, it works fine when I don't use react-router(1.0.0-rc3), but when i introduce the router, specifically a component that renders Links like this:
const React = require('react');
const Link = require('react-router').Link;
module.exports = class About extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<ul>
<li><Link to="/about">About</Link></li>
<li><Link to="/list">List</Link></li>
</ul>
);
}
}
The output differs on server and client, I get this warning
Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
(client) n><a class="" href="#/about" data-reacti
(server) n><a class="" href="/about" data-reactid
So the server(or client) renders the href tag differently, this is how I render the server side
const React = require('react');
const reactDOMServer = require('react-dom/server');
const tmpl = require('blueimp-tmpl');
const fs = require('fs');
const path = require('path');
const templateFunction = tmpl.tmpl;
const match = require('react-router').match;
const RoutingContext = require('react-router').RoutingContext;
const routes = require('../../app/routes');
const App = require('../../app/app');
const serverProps = require('../../server.props');
templateFunction.load = function(id) {
const filePath = path.resolve(serverProps.publicPath, id);
return fs.readFileSync(filePath, "utf8");
};
module.exports = function*(next) {
match({ routes, location: this.url }, (error, redirectLocation, renderProps) => {
if (error) {
this.throw(error.message);
} else if (redirectLocation) {
this.redirect(redirectLocation.pathname + redirectLocation.search);
} else if (renderProps) {
const html = reactDOMServer.renderToString( <RoutingContext {...renderProps} /> );
this.body = templateFunction("index.html", html);
} else {
this.throw(404);
}
});
};
the template engine i use here is blueimp-tmpl, i first suspected that it might do something to the href-hash-sign upon rendering, but i logged the output renderToString and the href-hash-sign is already gone before going into the template.
I did some digging in the npm history package(its a peer dependency for react-router) it seems to be the component that generates the href part of the Links, couldn't figure out why it renders it differently though.
Any ideas?
Edit, this is the routes
const React = require('react');
const Router = require('react-router').Router;
const Route = require('react-router').Route;
const BaseLayout = require("./components/base-layout/base-layout");
const List = require("./components/list/list");
const About = require("./components/about/about");
module.exports = (
<Router>
<Route path="/" component={BaseLayout}>
<Route path="about" component={About} />
<Route path="list" component={List} />
</Route>
</Router>
);
BR twd
Ok, solved it.
Initial problem, that the href on client side got hash tag type links was because of missing history config solved by:
// router.js
const React = require('react');
const Router = require('react-router').Router;
const Route = require('react-router').Route;
const BaseLayout = require("./components/base-layout/base-layout");
const List = require("./components/list/list");
const About = require("./components/about/about");
// This was the missing part...
const createBrowserHistory = require('history/lib/createBrowserHistory');
module.exports = (
<Router history={createBrowserHistory()}>
<Route path="/" component={BaseLayout}>
<Route path="about" component={About} />
<Route path="list" component={List} />
</Route>
</Router>
);
Links looked good now, but this caused the render to fail on server side because I used the entire Router when rendering and the createBrowserHistory requires the DOM to work. Solved by pulling out the routes to a different file like this:
// routes.js
const React = require('react');
const Route = require('react-router').Route;
const BaseLayout = require("./components/base-layout/base-layout");
const List = require("./components/list/list");
const About = require("./components/about/about");
module.exports = (
<Route path="/" component={BaseLayout}>
<Route path="about" component={About}/>
<Route path="list" component={List}/>
</Route>
);
And using the pure routes on the server like its shown here Server Rendering Docs. This made the server side rendering work, but now i broke the client side rendering again because my Router component did not understand how to use the routes that was declared in a different file, it was fixed easily by using the correct syntax:
// router.js
const React = require('react');
const Router = require('react-router').Router;
const routes = require("./routes");
const createBrowserHistory = require('history/lib/createBrowserHistory');
module.exports = (
<Router routes={routes} history={createBrowserHistory()}>
</Router>
);