React Server Side Rendering (SSR) Boilerplate.
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
- 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.
- Aspects lf authentication needs to be handled on server. Normally this is only on browser!.
- Need some way to detect when all initial data load action creators are completed on server.
- 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 ofclient.js
file cllient. Useimport 'babel-polyfill';
on the top of both files.