React server-side rendering with React-router v4 and Webpack v2 (post not finished yet)

Here is how I do react server-side rendering with react-router v4.

I’ll try to include just code. With minimal text.

Stack given:

  • Node.js
  • Expressjs
  • React
  • React Redux
  • Webpack v2.x

As we know, to perform react server rendering you need to have two versions of your react app — one for a browser, other one for a server. Well, you actually create only one react app. But the most dodgy and tricky task then is to organize your react app so that you can require your own components, and react-router components, and redux stuff (and probably other stuff, like react-bootstrap or FB JavaScript SDK stuff, etc) both, withing a browser environment and within your Express app (which is a pure Node.js environment that isn’t aware what a DOM, a window or a document objects of a browser environment are).

My app has a default Express directory structure that is generated by express-generator. Then, within the root directory of my Express app, I create another three directories:
/src — a root directory for my react app source code, all my own components, redux, react-router, etc. live here
/dist — react components compiled by Webpack, so that I can use them in Node.js environment.
/webpack — Webpack configs to compile stuff from /src to /dist, and both, for browser and server environments.

I broke my react app into four modules (four separate files):
* Reducers (reducersFactory.js)
* Router (router.js)
* Routes (routes.js)
* State (state.js)

And I create two additional files: browser.js and server.js. For a browser and a server environments respectively. These files are entry points for my two Webpack configs — for a browser and for a server environment.

browser.js

  1. It is a final point for browser environment. It renders the app into a target DOM node. This file doesn’t export anything. It renders the app. That’s why it is a final point.
  2. This file is an entry point for Webpack config that compiles my app for a browser environment.

reducersFactory.js

reducers are a part of redux.
I keep all reducers in one function. redux has combineReducers method. But I don’t use it. It is PITA to break and assemble reducers with that method. I’ll explain why in a separate blog post.

The reason I made my reducer as a separate module is because now I can require it in both browser.js and server.js files, that is for a browsers and server environments.

I export a function that, when called, will return a redux reducer function.

router.js
This is my custom router. Again, it is a separate module because now I can require it in both environments.

It combines both types of react-router components: BrowserRouter and StaticRouter. You can break it into two separate files, of course. But I prefer keeping it in one module — they are small, don’t have much functionality. I pass my router anenv prop to distinguish routers.

I export a function that, when called, will return a one the routers, either BrowserRouterorStaticRouter (which are react components already! not functions of classes).

routes.js
This is where my top-most App component and all my app routes live. Having my routes as a distinct module let me change, amend, adjust, modify them later how I want without changing the whole app itself. Should I have my app monolithic as all those react tutorials recommend (Router, Provider, Store, State all live in one file), it wouldn’t be so flexible and granular.

  • Also, it allows me to require this module within my server Express app.
  • Also, this is the only module in which I import my own top components that comprise my routes. This is very important for me, because this way I have only one point where I require them. So I know where to find it immediately.
    Of course, components may import (and surely will in a big app) other components, but I talk about top components that comprise my routes — they all live here only.
  • Also, with react-router v4 you don’t do this any more, don’t nest <Routes>:
<Route component={App}>
<Route component={Index} />
<Route component={About} />
</Route>

You do it like this now. App embeds your routes as-is:

<App>
<Switch>
<Route exact path="/" component={Index} />
<Route path="/about" component={About} />
</Switch>
</App>

Again, I export routes as a function. When called it returns my App (as a react component! not a function or a class). With all the routes in it (as reacr components as well).

TO BE CONTINUED…