I know, I know. This title sounds cocky. In fact, it makes a lot of sense if you think about it. I have been asked multiple times by my friends from our local React meetup group I am organizing or by teams I am helping to develop their applications for a starter, boilerplate or a setup. This post is a result of another such question. Most of the time it is one of the two scenarios.
In the first scenario, developers are looking for a way to start a project and this is the problem
create-react-app is trying to address. It is an official command line tool which is a recommended way to start a react project. It works well for people who are starting with React as it abstracts a lot of complexity connected to the configuration of babel, webpack etc. For more serious, production use when you require having more control over your build,
create-react-app provides also an
eject script which unpacks a lot of logic initially hidden in
react-scripts package. At that point, complexity initially hidden is often intimidating and that is only the tip of the iceberg. Now you probably would like to have library for state management, server side rendering and so on. Nevertheless, if you want to test a new library or learn about React
create-react-app is a great choice.
In the second scenario, application is already under development, but no one on the team is comfortable with it and perceives “code around webpack” as strange and extraneous. The core of the application is one of the popular boilerplates cloned at the beginning of the project. Often there was never a time to understand it from the ground up and get a holistic picture of what is going on.
This a little long introduction leads us to the merit of what I am trying to say. I believe that the best boilerplate is the one you do yourself. Initial investment in putting puzzles together pays off in a confidence, ease of solving later problems and overall maintenance time.
So, what I am going to do is take my minimal setup (webpack 2 + hot reloading) and start building upon it. At the end I will have Redux, React Router and Helmet playing nicely together on both client and server.
If you do not like it, that is fine, it was created to make my life easier and does that great. You can always use
create-react-app instead and
Entire code is available on GitHub: react-boilerplate-lite#not-so-lite.
Routing is an important part of our setup as it has to be integrated on both client and server side. React Router is de facto standard way to manage routes in react applications and it supports server side rendering.
I am going to use the most up-to-date version, React Router v4, but if you are already using the previous version follow instructions from corresponding docs or update. Migration paths are straightforward, so I would recommend updating. We want to use the same routes definition on both client and server, but we need different routers. On the client side application should be wrapped in a
<BrowserRouter> and on the server side in a
<StaticRouter>. Let’s start with defining routes in the top-level component.
Now, let’s edit render function in bundle entry file and wrap routes in
BrowserRouter is using HTML5 history API, so there is need for explicitly passing history like in previous React Router version. Implementation supporting hash portion of the URL has been moved to
HashRouter. We will get back to render function later when integrating Redux.
Server Side Rendering: Router
To render application on the server we need few changes to how project is built. We could try to use app’s source files directly in Node. Major problem with this approach is that it becomes hard to supplement webpack’s features like chunks and various loaders. Whether it is possible or not depends on to what extend your application is coupled with the build process.
I am using
css-loader modules, I like to
require my images and video files and then use a list of assets in my Service Workers for caching. I would say my apps are tight to webpack. The best solution in such case is to create separate server build working when run by Node. Webpack config for server is similar to the production one with different entry and output.
Once we have a suitable build we can create render function used on the server. I am using
HTMLWebpackPlugin for generating
index.html file and we can use it as a template on the server. We could also use templating engine, but we can get away without it. Depending on whether we serve files from memory during development or from hard drive on production we need a different way to read this file. To read template during development we use
webpack-dev-middleware. As JSX is not supported natively in Node I am using
React.createElement to wrapp App component with StaticRouter. During development we want to make sure that render is always using the newest version of the server bundle, so we delete it from
require.cache. We do not have to worry about it in production as we do one build per release.
Although the page is rendered, server is not aware of 404 errors and cannot perform a redirect. When page is not found or redirect should be performed page is going to be rendered like usually with 200 state. To fix it we can use context which can be modified mutating
staticContext which is passed to each route on render. Let’s start with creating a generic component which allows setting correct status code.
We can use Status generic component for Not Found page and redirects.
On the server in render function we have to check whether
context has a url set. If it has that means redirect was matched by router. Then we can perform redirect with correct status. If
context.url is not set then we just render application with given status, 200 by default.
Helmet is a great little library providing react component for managing all changes to document head. We can use it for setting title or different meta tags.
After page is rendered to string we can obtain plain HTML strings from Helmet and put them into our template. The one caveat is that we have to use the same library instance. To do that we could use bundled version of Helmet or do something opposite, exclude
react-helmet from the bundle making it an external dependency. Without this step Helmet would just render an empty element as an application would be rendered with bundled instance and
renderStatic would be called on the instance from
That is it!
For this setup I am going with Redux as it provides a solution for preloading state which makes things much simpler when server rendering. You can do the same with MobX, but due to not centralized state (multiple observables) it requires more glue code.
I am not going to use any library for effects/async actions like redux-thunk, redux-loop, redux-saga or redux-observable as this is a per-project changing dependency. As far as I am concerned it does not make much sense to include any in a boilerplate. On the other hand, if you are a true believer of any of those, go ahead! Each library has a good documentation and plenty of examples so you should not have any problems to set it up. You can always google some boilerplates which implemented one of those for the sake of reference.
Later on we make preloaded state globally available. Do not forget about replacing store’s reducer once it changes.
Server Side Rendering: Redux
Server Side Rendering with Redux comes down to providing preloaded state for a given route. There are few possible approaches. Data dependency might be defined by a current Route or resolved by particular route handler on the server. I am not opinionated, which approach you choose highly depends on whether your Node application can access database. If that is not the case letting route decide has an advantage of defining required data (e.g. through GraphQL fragment) or requests (e.g. axios is universal) in one place.
To preload state we need to create a store, to do that we need a root reducer. Let’s add root reducer as a second entry point.
Store is created in render function with preloaded state applied. Root reducer also shouldn’t be cached so a new version is used every time. If you use connect from react-redux you need to wrap the application in Provider and pass a store in props. Finally, we have to make state globally available, so it can be picked up by store created on the client side.
It is a lot of steps and it is not trivial to put it together! That said, it pays off, with interest. Entire code is available on GitHub: react-boilerplate-lite#not-so-lite.
If you do not mind giving away the fun of setting it up and/or you are intimidated by complexity you can consider using next.js. You will the pay price of being coupled with the framework, but credit should be given where it is due. Guys from ZEIT are doing great work in Open Source!