React Server Side Rendering (SSR) Boilerplate.

Weerapat Chulaket
MFEC
Published in
4 min readJun 21, 2018

So if use SSR with ReactJS, don’t have to wait for JavaScript to load and get fully rendered HTML as soon as the initial request returns a response. (It’s a good thing for SEO).

Creating a boilerplate.

Setting a package.json file as below and use npm install or npm i .

"dependencies": {
"axios": "0.16.2",
"babel-cli": "6.26.0",
"babel-core": "6.26.0",
"babel-loader": "7.1.2",
"babel-preset-env": "1.6.0",
"babel-preset-es2015": "6.24.1",
"babel-preset-es2017": "6.24.1",
"babel-preset-react": "6.24.1",
"babel-preset-stage-0": "6.24.1",
"compression": "1.7.0",
"concurrently": "3.5.0",
"css-loader": "^0.28.11",
"express": "4.15.4",
"express-http-proxy": "1.0.6",
"lodash": "4.17.4",
"node-sass": "^4.9.0",
"nodemon": "1.12.0",
"npm-run-all": "4.1.1",
"react": "16.0.0",
"react-dom": "16.0.0",
"react-helmet": "5.2.0",
"react-redux": "5.0.6",
"react-router-config": "1.0.0-beta.4",
"react-router-dom": "4.2.2",
"redux": "3.7.2",
"redux-thunk": "2.2.0",
"sass-loader": "^7.0.3",
"serialize-javascript": "1.4.0",
"style-loader": "^0.21.0",
"webpack": "3.5.6",
"webpack-dev-server": "2.8.2",
"webpack-merge": "4.1.0",
"webpack-node-externals": "1.6.0"
}

create webpack.base.js file for merge config server and client. Then copy as below.

const ExtractTextPlugin = require('extract-text-webpack-plugin');module.exports = {
// Tell webpack to run babel on every
// files it runs through.
module: {
rules: [
{
test: /\.(js|jsx)?$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: [
'react',
'stage-0',
['env', {
targets: { browsers: ['last 2 versions'] }
}]
]
}
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader!sass-loader",
})
},
]
}
}

create webpack.server.js file for config the server side application. Then copy as below.

const path = require('path');
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base.js');
const webpackNodeExternals = require('webpack-node-externals');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const config = {
// Inform webpack that we're building a bundle
// for nodeJS, rather than for the browser.
target: 'node',
// Tell webpack the root file of
// our server application.
entry: './src/index.js',
// Tell webpack where to put the output
// file that is generated.
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'build'),
},
externals: [webpackNodeExternals()], plugins: [
new ExtractTextPlugin({
filename: './styles/style.css',
disable: false,
allChunks: true
}),
],
};
module.exports = merge(baseConfig, config);

create webpack.client.js file for config the client side application. Then copy as below.

const path = require('path');
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base.js');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const config = {
// Tell webpack the root file of
// our server application.
entry: './src/client/client.js',
// Tell webpack where to put the output
// file that is generated.
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'public'),
},
// Tell webpack where to put the output
// file that is generated.
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'public'),
},
plugins: [
new ExtractTextPlugin({
filename: './styles/style.css',
disable: false,
allChunks: true
}),
],};
module.exports = merge(baseConfig, config);

create script in package.js file for run the applcation.

"scripts": {
"dev": "npm-run-all --parallel dev:*",
"dev:server": "nodemon build/bundle.js --watch",
"dev:build:server": "webpack --config webpack.server.js --watch",
"dev:build:client": "webpack -- config webpack.client.js --watch"
},

JSX on the server run webpack on all of our server side code, then execute resulting bundle. So, we need to turn components into HTML by use the react-dom/server libraries renderToString function.

Note

Server Side Rendering (SSR) is generated HTML on the server. But Universal Javascript and Isomorphic Javascript is the same code run on the server and the browser.

This will have build/bundle.js for only run on the server side and public/bundle.js for only run on the client side.

Warning

Replace the ReactDOM.render() call with ReactDOM.hydrate() if you want React to attach to the server HTML.

Router Section

The server side use StaticRouter when doing SSR and the client side use BrowserRouter when running in a browser. Example in the client.js file as below.

// Startup point for the client side applcation.
ReactDOM.hydrate(
<Provider store={store}>
<BrowserRouter>
<Routes />
</BrowserRouter>
</Provider>
, document.querySelector('#root')
);

So, see that at the client will use BrowserRouter and the Routes file for configure about route path such as <Route exact path=’/home’ component={Home} /> like so.

Note

If you have warning about this below.

It because you didn’t set route config in server router. So, use the StaticRouter libraries for solve it. Following route configuration the server side. So, a parameter that recieved (req) , it had sent from root file of the server side index.js

// index.js Root file server side.
app.get('*', (req, res) => {
res.send(renderer(req));
});
//---------------------------------------------------------------// renderer.js for helper to config server side.
export default (req, store) => {
const content = renderToString(
<Provider store={store}>
<StaticRouter location={req.path} context={{}}>
<Routes />
</StaticRouter>
</Provider>
);
return `
<html>
<heade></head>
<body>
<div id=’root’>${content}</div>
<script src="bundle.js"></script>
</body>
</html>
`;
}

Redux Section

Note: 4 Big Redux Challenges

  1. Redux needs different configuration on browser vs server. So, this will use 2 stores, one store for a browser and one more store for the server.
  2. Aspects lf authentication needs to be handled on server. Normally this is only on browser!.
  3. Need some way to detect when all initial data load action creators are completed on server.
  4. Need state rehydration on the browser.

Browser Store Creation

So the browser side, will do in client.js file.

const store = createStore(reducers, {}, applyMiddleware(thunk));

Server Store Creation

So the server side, will do in helpers/renderer.js file that contain all of the server need.

const content = renderToString(
<Provider store={store}>
<StaticRouter location={req.path} context={{}}>
<Routes />
</StaticRouter>
</Provider>
);

Example for actions in the application

export const fetchUsers = () => async dispatch => {
const response = await axios.get('URL HERE');
dispatch({
type: FETCH_USER,
payload: response
)};
};

Note: Add the top of index.js file server and top of client.js file cllient. Use import 'babel-polyfill'; on the top of both files.

Thank you!

Writer By Mr.Weerapat Chulaket — MFEC PEOPLE

--

--