First and foremost I would like to give thanks to Vincent De Snerck and Saïd Tayebi, who helped with this setup.
What will you get out of this?
This article will give you the building blocks to setup a modern web development stack using:
- ReactJS (0.14)/React-Router (1.0.*)
- Redux (3.*)
- NodeJS (4.0.0)
- WebPack (1.12.*)
This article will be split up in separate parts:
- Basic setup: React, React-Router, WebPack, Server-Side rendering
- Redux integration: Redux stores, dev tools, etc.
- Redux-flow optimized
The entire codebase of part one can be found here
Note to readers: there is a separate branch for the babel6 integration below, this branch It’s best to use that one as your starting point, since it’s more future-proof.
Prerequisites
- ReactJS experience (beginner/intermediate)
- ES6 knowledge (ES2015)
- Patience
- A cup of coffee
Note to readers
Feel free to contact me or open pull requests on the repo if you have a better implementation or find something wrong in the setup.
Introduction
ReactJS is this years game-changing Javascript framework, and it inspired a lot of new libraries that in conjunction with the framework could be used to create full-stack Javascript websites/applications.
So why this setup? Most setups online cover all the basics. Heck react-isomorphic-starterkit give you all you need for React, with Server-side rendering out of the box. However, we wanted to implement a custom setup based on all open-source material we found, that could do it all with a Redux-architecture backing it.
In this part I’m sharing the initial setup that can accomplish SSR with React, WebPack and a NodeJS server.
Part 1: Basic Setup
There are a lot of great articles that explain how ReactJS/Redux/… work, what the benefits of the framework are and so forth. I do not wanna bore you with a tedious re-iteration of all those concepts, so I simply linked to valuable resources at the bottom.
In this setup we’re looking at implementing ReactJS with JSX transformation, WebPack bundling, and ES6 usage.
Backwards compatibility to “legacy” browsers with polyfills is not taken into account in this setup.
If I had to give a rough compatibility estimate without using wrappers and/or polyfills I would say IE10 and up.
Architecture
We need our front-end application and backend server. The front-end app will contain all ReactJS/CSS styling and the backend will spin up a web server with WebPack middleware to compile the assets.
We’ll be building everything from the ground up. Starting with a basic React app spun up by a node server, all the way to advanced routing with server-side rendering. Some steps will be more detailed then others, but because I consider you to have a certain level of knowledge, I will not be going through the pure basics.
1) The Structure
So what we want to do is spin up a basic React App with Server-side rendering. Let’s get our folder structure right:
1.1) The App
The app consists of the following setup:
- A basic widget
- Widget specific and general CSS generation
- Linting for JS and SCSS (to be used if you want it)
- Development build-server
1.1.1) The Starting point
The starting point of our app is “app.js” and “index.html” this will contain the logic that spins up our application.
This file loads in the most important modules and renders the react router to the root DIV of your index page, that looks like this:
The chunks reference here will be filled in by the htmlWebpackPlugin.
As you can see in root we have a EJS control flow. This template engine will generate the HTML that is to be placed between “<%- reactOutput %>”. This however is only important for server-side rendering. Which we will cover later in this article. For now just leave it there, React will render over it. You could also put the ID “root” on your body, but this does not allow for extensibility, since later down the line we would possibly want to add a footer, menu, devtools, etc.
1.1.2) Main Component
We also need a React component that you want to render to the screen:
What this file does, is represent a React Component that wraps around all your other components. “{this.props.children}” will render all child routes to that specific position.
Let us also state clearly here that we have a differentiation between smart and dumb components. Smart components being components that are aware of their state, do data fetching, etc. Dumb components being components that receive and show data but have no notion of any mutating state.
In practice this could be, for example: A smart component (container) that receives live updates of data and a dumb component would be a component within that component that you push the data to, to visualize it in a specific way. For instance a graph.
It could be visualized like so:
1.1.3) Routing
As you could see above we are using React Router to accomplish the routing. There are many other ways, but it seems to be the standard to render complex view-scenarios.
They have great documentation on how to accomplish routing in many different ways. I chose to implement the “large app” approach. Since this setup needs to be able to scale. I do divert a little from the base implementation.
The way you set it up is you include a “routes” folder. This folder contains subfolders that hold the components that will be rendered to the screen.
We need to make a folder “routes” with its respective “index.js” file. This will export all our routes.
Let’s say you wanted to implement a Feed with an list of items. you could immediately extrapolate 2 components from this sentence: “Feed” and “Item”. Let’s set that up:
So what we do is we make a folder “feed” under routes. In that folder we make a folder “components”. We also need an “index.js” file to export the route and component linked to that route. This will be our main import file.
Here I export an object with 2 paths that are all children of the common-path “/feed”.
Now what you may notice is that there is component-specific CSS imported here. However the way it is imported it different than normal. Babel does not like importing CSS. Since it does not understand the syntax, we accomplish importing this through checking a process variable called “process.env.BROWSER”. In WebPack you are able to define variables that are app-wide.
1.2) WebPack Configuration
Now that we have the basis for our web-app we should look at how to get it running in the browser.
We’ll be using WebPack as the module bundler. It will handle the development environment with ES6 transpilation, hot-reloading, etc.
If you use CommonJS, AMD or ES6 modules, WebPack bundles all these resources and generates the necessary code so the browser can interpret your Javascript.
WebPack has a lot more benefits, like code-splitting, optimizations, basic loaders to support: css, text-files, images, JS. It takes away a lot of the ‘complexity’ you had when setting up a Grunt or Gulp pipeline. However, it takes some getting used to.
A key difference here from other tutorials on WebPack and React is that our configuration does not use WebPack Dev Server. It is a good tool and can be used to accomplish the exact same setup. However, it does not allow the same flexibility, so we will use the WebPack development middleware instead.
The first thing we need to do is define our WebPack configuration file for development. Named “webpack.dev.js”.
The following plugins will be used:
The ExtractText Plugin is used to generate a separate file from loaders that return text. We define the configuration for it in this block.
The Define Plugin is a WebPack module that can be used to declare global references that only WebPack can understand. We’re using it to declare our development environments.
The Html Plugin is used to copy your html file to the dist folder, we use this because WebPack does not re-reference script tag src’s by default. This plugin makes it possible to link to the output files.
The HotModuleReplacement Plugin includes a feature to inject updated modules at runtime. It does this using a socket connection.
The OccurrenceOrder Plugin this will order the modules and chunks by occurrence in an effort to try and save space for modules that are referenced often.
For a line-by-line summary please click here.
This configuration on it’s own is not enough! This can in theory bundle and serve, had we used WebPack Dev Server. But let’s do it the hard way!
1.3) The Server
As you could see in the “package.json” above, we will be using ExpressJS to spin up a web-server.
So we do a couple of things here that are interesting. First and foremost we add the babel require-hook at the top so all files that this file includes can be written in ES6.
Then we delete process.env.BROWSER so when our server-side rendering passes through our components it doesn’t crash on importing SCSS.
We do all the basic imports for Express, some middlewares, etc. We have our own utilities folder, which we will be setting up later. We also have a router which will cover the routing of urls on the backend and do the server-side rendering.
We expose our WebPack middleware which in turn exposes hot reloading and the bundler.
We also use a library called piping, which uses NodeJS’s cluster API to spin up the server whenever files are changed. Now this line is necessary when you are testing SSR. The downside is that WebPack hot reloading will not work properly since you’re basically spinning up a new server in the background on each change and switching daemons on the fly. WebPack processing occurs first however, when the server goes down to switch processes you lose hot reloading. So in practice you should turn off the piping line when you want to build components fast without testing SSR. But uncommenting and re-commenting a line is prone to error, that’s why we use a environment SSR flag.
1.3.1) Utilities
Let’s setup a utilities folder as “utils”. This will be a simple folder with all utilities exporting functions and an index file to allow ES6 destructuring.
We are exporting variables to check the environment, but we also need to check if the SSR flag is on. If it already has a value from the system it’s automatically true, otherwise we’ll check if we passed an argument while starting the node server to enable server-side rendering.
1.3.2) WebPack Middleware
Our middleware is somewhat more complex. Since what we want to accomplish to do SSR is retrieve the index.html that is built by WebPack on the fly so you always have the most recent version if you were to apply adjustments to it. This was the main drawback from using the dev-server. You could not retrieve the files that were built, because they are built in-memory.
What this does is spin up a WebPack compiler that bundles everything and expose a “query” method that reads a file from the WebPack memory. It also exposes the HotReload middleware for WebPack. We also place a custom header so we know it was rendered by WebPack: “X-Webpack-Rendered: yes”
1.3.3) Routing middleware
We need define our routing middleware that will poll either from the WebPack filesystem on development or a static folder on production. It will then render the correct components for the route requested and generate the corresponding HTML.
Now what this does in detail is we import all the routes from the routes folder that we use in the “app/” folder. Then we use react-routers match function to match the request route to the routes we have defined.
Based on the result we will either receive an error (because the route is not found), a redirectLocation (which we will redirect to so we can render the correct path), or renderProps which contain the components that need to be rendered.
1.3.4) Render engine
As you can see in the code above we call the renderEngine that takes our renderProps and html body and returns a promise that contains the html we’ll send back to the client.
This is an important step, since it’s the crucial part of SSR. What we need to do is create a folder “engines” this will contain our renderEngine helper. Why call it engines? Well, because it takes in an input and runs it through a process that mutates the data and outputs the data we need to send back to the client. Just like an engine burns gas to move forward, the renderEngine takes in the properties necessary and outputs what is needed to let the website render properly.
Above you see 2 methods: _renderComponents and a default export function that will do the actual rendering. The prior will render the renderProps with ReactDOMServer which is a package that React made, it traverses over the components and creates the HTML output for them. The latter uses the EJS engine to render the corresponding HTML to they body we provide it. So it basically takes the body, looks at the markup and says “huh, here’s <% reactOuput %>, let me post this output there and return the HTML. Why wrap it in a promise? It provides a cleaner syntax then a callback in my opinion. EJS is completely synchronous, but adding the asynchronous nature to it allows extensibility down the line.
If SSR is turned off it will return an empty HTML page, so no conflict is possible when React renders in the front-end.
We now have a complete stack to run our app on. If you run: “npm run serve” and surf to “http://localhost:9000/feed” you will see a nice shiny app running.
That’s it for our setup! I hope you enjoyed this article.
In part 2 we will be introducing a decoupled redux architecture that is easily testable and shareable. We will also be integrating SSR that will fill up the store with all asynchronous calls resolved before rendering to the client. This way the client will have an initial state.
Useful resources
Isomorphic stack AirBNB:
http://nerds.airbnb.com/isomorphic-javascript-future-web-apps/
Handcrafting isomorphic apps (a really good post): https://medium.com/front-end-developers/handcrafting-an-isomorphic-redux-application-with-love-40ada4468af4#.z7j8596v4
The WebPack and ReactJS documentation a really great resources as well!