Server-Side Rendering with React, Node, Webpack & Babel
I am going to go over how to create an SSR React website. To achieve this I will be using React 16, Node 10, Webpack 4 & Babel 7 🎉.
Here is a boilerplate project for reference [Link to repo🔥].
CSR or SSR 🚨
CSR stands for client-side rendering, this is when websites are entirely rendered in the browser with JavaScript. This is the way in which Front end frameworks such as React work by default.
SSR stands for server-side rendering, this is a technique where your Javascript code is sent down to the browser as pre-rendered HTML.
There are different use cases for each (you can find many articles debating this [here]).
In the boilerplate I have provided you can toggle between both options via an optional Environment variable❤️.
The main focus of this is to provide an easy to follow resource for getting your own SSR project up and 🏃🏼♂ ️running.
SSR is great for SEO as it means your mark up is not rendered in the browser allowing the ‘Search Engine Bots’ to fully crawl your content, Also if for some reason one of your users has javascript disabled ☠️ they will still be able to view your website in all its glory 🚀.
I'm going to break this process down into 3 steps.
Remember to clone the boilerplate project and run it locally [Link to repo🔥].
1- Initial Set up 🧰
The first thing we need to do is set up our configuration files (Webpack, package.json & .babelrc).
Webpack
Webpack is a module bundler for modern JavaScript applications, most often used for the web but not restricted to it. It is a great tool which allows for code splitting and hot module replacement amongst many other things. See [here] for more info.
Unlike previous versions of Webpack, Webpack 4 is a lot easier to get to grips with due to the lack of a requirement for a configuration file.
In this project I have used a configuration file [click here to see it] to extend the built-in functionality by using the following:
- babel to transpile our Javascript
- file-loader to resolve imports of our images
- sass-loader to load our SCSS files and compile it to CSS
- react-hot-loader to tweak our React components in real-time
Babel
Babel is mainly used to convert ECMAScript 2015+ code into a backward-compatible version of JavaScript in current and older browsers or environments, .bablerc is a configuration file [click here to see it] for using specified features of Babel.
The features of note that I have used are:
- @babel/preset-env is a smart preset that allows you to use the latest JavaScript without needing to micromanage syntax transforms.
- @babel/preset-react is a compilation of presets for all React plugins.
- @babel/plugin-proposal-class-properties is a plugin for transforming static class properties as well as properties declared with the property initializer syntax.
- react-hot-loader/babel to tweak our React components in real-time.
- transform-assets transforms importing of asset files at compile time using Babel. This plugin removes the need to run your server code through Webpack module bundler when using loaders such as file-loader.
Package.json
The package.json file holds various metadata relevant to the project. This file is used to give information to NPM that allows it to identify the project as well as handle the project’s dependencies [click here to view it].
2- The Node Server 👾
Babel
Before we start our server it is important we load all of the required Babel packages, we handle this in our root index.js file [click here to view it]. Once we have done this we call our serve() function which gets us up and running.
The serve() function starts up our Node Server which is created in [this file].
Express and middlewares
The [file] which sets up our Node Server has a few dependencies which all have there own important role to play.
- path provides utilities for working with file and directory paths
- express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.
- helmet helps you secure your Express apps by setting various HTTP headers.
- response-time is a middleware that records the response time for requests in HTTP servers
- cookie-parser lets you parse Cookie header and populate req.cookies with an object keyed by the cookie names.
- cors allows you to enable CORS with various options
- body-parser allows you to parse incoming request bodies in a middleware before your handlers
- express-handlebars is a view engine for Express which doesn’t suck.
We have two very important files in our project handleCSR and handleSSR, these handle how our mark up will be returned to the end-user.
handleCSR
There actually isn't much to this file [click here to view]. We have a HTML file stored in our dist directory [click here to view], we use path to get the HTML file and then render it out to the browser. You may notice we are passing a second parameter to the render function, This isn't essential but allows us to assign a selection of our Environment variables [click here to view] to the window allowing our React app to use them.
handleSSR
This [file] is a bit more complex than its CSR counterpart. It mimics a lot of what you will see in the client-side React code. One thing to note in this [file] is that we are importing react-dom/server, this enables you to render React components to static markup.
The way the SSR file works is:
- it gets the requested URL location
- creates an initial state
- sets up the redux store and dispatches the current location
- gets the current state from the redux store
- uses ReactDOMServer.renderToString to create the HTML string of requested react component.
- creates the HTML string for the selected page by injecting the stringified React component into a template string HTML document.
- Returns this to the browser ✅
3- React / Redux setup 🏡
The very final piece of all this is the part I'm sure you are all familiar with. All of our React / Redux code is within the src directory [see here].
Our Redux store is configured [here].
The configure store functions first parameter is ‘fromServer’. When we are creating the store in the browser we pass this as false and use history/createBrowserHistory however if we are creating the server-side then we pass true and make use of history/createMemoryHistory. This is since the server has no HTML5 push states, history must be temporarily created in memory.
In this project, there is only one React component [here]. There is also a bare-bones example of redux actions, reducers and sagas for this component [here].
Running the project
Once you have cloned the boilerplate project you will first need to install all of the dependencies by running ‘npm install’.
There are two scripts you can run to start the project:
- npm run dev: This will start the project up using [Hot Reloading] allowing for fast feedback in the Browser as you make changes to the UI. If you use this method to run the project you will bypass the node server and everything will be ran using Webpack dev server on http://localhost:8080/home
- npm run start: This will by default start the project up using SSR, if you wanted to use CSR then you can create a .env file and set the SERVER_RENDERED flag to be false.
You can see SSR in action by …
- starting up the project using npm run start
- navigating to http://localhost:3000/home
- opening chrome dev tools
- disabling javascript
- refreshing http://localhost:3000/home you will see that you still are presented with your component🔥🎉.
As you may be aware if SSR was not in action then we would see a blank screen with Javascript disabled.
Conclusion
I hope this helped you see what needs to be implemented to get a real-world SSR React app up and running 🤞🏻.
Any issues get in touch via the boilerplate repo [Link to repo🔥].