Migrating to react-router v4…the SSR way

Frances Ng
Frontend Weekly
Published in
2 min readMay 3, 2017

Migration.
Lots of migrations recently.
Lots of major migrations.

Migrating to React v15.5 is a good preparation for the full embrace of React v16 (:scream), and it wasn’t too much of a pain as it was mostly with React.PropTypes deprecation and some other API deprecations.

react-router v4 is a different case.
It is almost exactly a new way working with the routes. Everything now is a component. The authors explained. Now you have Inclusive/Exclusive routes, that in some way you don’t have to pass {this.props.children} anyway. It gives the flexibility of what we would have imagined the router to be at first place, with the essence of SPA being component-based.

BUT…migrations are often painful. It is THE time to actually learn more thoroughly on the topic itself, so it has got to be painful. v3 -> v4 is different. and it will break stuff, especially with SSR!

The Docs actually have it written down. Now you need 2 types of router :<BrowserRouter> on the Client side and <StaticRouter> on the stateless server side.

v3

const ReactRouterContext = React.createFactory(RouterContext);
const ReduxProvider = React.createFactory(Provider);
match({ routes, location: req.url }, (err, redirectLocation, renderProps) => {
if (err) {
res.status(503);
} else if (redirectLocation) {
return res.redirect(302, redirectLocation.pathname + redirectLocation.search);
} else if (renderProps) {
return res.send(`<!DOCTYPE html>${ReactDOMServer.renderToString(
ReduxProvider({ store }, ReactRouterContext(renderProps)))}`);
}
return null;
});

but in v4, match no longer exists, as StaticRouter handles it all on the server side. All we need to do is to render the same Router configurations in the server as in Client to match the checksum and that’s it.

   if (context.url) {
return res.redirect(302, context.url);
} else {
const renderHTML = ReactDOMServer.renderToString(
ReduxProvider(
{ store },
serverConfig(req, context)
)
);
return res.send(`<!DOCTYPE html>${renderHTML}`);
}

with serverConfig being something like this. This only needs to be rendered on the server side.

const serverConfig = (req, context) => (
<StaticRouter
location={req.url}
context={context}
>
<App />
</StaticRouter>
);

In order to match the checksum, a client-side version router is needed of course.

const history = createBrowserHistory();
ReactDOM.render(
<Provider store={initStore}>
<Router history={history}>
<App />
</Router>
</Provider>,
document.getElementById('app')
);

Pairing Up Server and Client

  • Server: ReduxProvider(essentially createFactory -> props store in)
  • Client: <Provider>
  • Server: <StaticRouter>
  • Client: <BrowserRouter>
  • Server: [x] history (stateless)
  • Client: createBrowserHistory()

Keeping up the state

Server ReactDOMServer.renderToString with <StaticRouter> passes the state to server side App component -> render <script> with window.__INITIAL_STATE__ -> client side gets initial state from window.__INITIAL_STATE__ -> inject into <Provider store={initState}> -> <BrowserRouter> -> it’s a match!

--

--