Server Side Rendering React App with Express

Oğuzhan Kurtuluş
6 min readDec 16, 2020
Dive more, explore more…

If you are reading this article to find a solution to convert your client-side React App to Server Side React App, you are in “The Good Place :)”. I know that you dig lots of websites or articles to find out the best practice for you. Now just get relax, calm down and focus on what i am explaining you.

React is a very popular Javascript Framework for building single page web applications, user interfaces or UI components. This framework makes UI coding easier. In this article i will not talk about pros and cons, as you know because almost all frameworks have pros and cons. You can check the npm trends of modern frameworks from this link.

If you want to see the code directly, you can check my github repo “ogzico”…

Let’s continue… I suppose that;

  1. At the beginning you have created a client-side React App with “npx create-react-app <your-app-name>”.
  2. Then you developed your application and add lots of features. Implemented many libraries.
  3. Then when you want to go live, you have noticed that SEO (Search Engine Optimization) issue. || You have noticed that your initial page load is slower and you want to make it faster.
  4. You have searched how to make React App Server Side App. I think you have seen the great Next.js by Vercel. Yes this is so cool framework but you cannot use probably. Whyyyy? Because you should change almost all your code architecture and implementation. Then you need an easier way with not changing your beautiful base code.
  5. Express + Webpack + Babel and your beautiful React App. This is the solution what i want to explain you in this article. Some developers call it REXPack. I like this name.
  6. Let’s create REXPack to convert your client side app to server side React app or Isomorphic React App.
public/
favicon.ico
logo192.png
logo512.png
manifest.json
robots.txt
src/
Actions (...)
Components (...)
Reducers (...)
App.css
App.js
App.test.js
Html.js
client.js
reportWebVitals.js
server.js
setupTests.js
store.js
.babelrc
README.md
package.json
webpack.config.js

Above you see app skeleton. Please carefully consider this frame.

The most important part is webpack.config.js, because correct build make everything easier. Before start to code, i suppose you to check webpack documentation.

I know you have already understood the whole webpack configurations.. Then now let’s start to save our packages.

npm install express @babel/cli @babel/core @babel/polyfill isomorphic-style-loader react-redux redux webpack webpack-node-externals

Your dependencies list would be like this;

react dependecies
SSR React Dependencies

Also you should care about two important points. One of them is “engines” key, another one is “script” key. You should write correct values for these keys. Just follow below…

SSR React package.json engine config
SSR React package.json scripts config

Finally our package.json is ready to fly… Yes, this was the smallest part of our implementation. Now lets create our webpack.config.js file. The most important part is to tell the entry and output points. Also your rules can change because of your CSS preloader.

const path = require('path');
var nodeExternals = require('webpack-node-externals');
module.exports = [
{
mode: 'development',
name: 'server',
target: 'node',
externals: [nodeExternals()],
entry: {
server: [
'@babel/polyfill',
path.resolve(__dirname, 'src', 'server.js')
],
},
output: {
path: path.resolve(__dirname, 'build'),
filename: 'server.js'
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
include: [path.resolve(__dirname, 'src')],
},
{
test: /\.css$/i,
include: [path.resolve(__dirname)],
use: [
'isomorphic-style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
}
},
]
},
{
test: /\.(png|jpe?g|gif|svg|pdf)$/i,
include: [path.resolve(__dirname)],
use: [
// 'file-loader'
{
loader: 'file-loader?name=[name].[ext]'
},
],
},
],
},
node: {
console: false,
global: false,
process: false,
Buffer: false,
__filename: false,
__dirname: false,
},
},
{
mode: 'development',
name: 'client',
target: 'web',
entry: {
client: [
// '@babel/polyfill',
'./src/client.js'
],
},
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name].js'
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
include: [path.resolve(__dirname, 'src')],
},
{
test: /\.css$/i,
use: [
'isomorphic-style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
}
},
]
},
{
test: /\.(png|jpe?g|gif|svg|pdf)$/i,
use: [
{
loader: 'file-loader?name=[name].[ext]'
},
],
}
],
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: 'initial',
name: 'vendor',
test: module => /node_modules/.test(module.resource),
enforce: true,
},
},
},
},
devtool: 'hidden-source-map',
node: {
fs: 'empty',
net: 'empty',
tls: 'empty',
},
}
]

We are ready to create our server.js, client.js and Html.js files to make this process completed. Our server.js file will be the first entry point which will create our static HTML file thanks to Html.js file. After first rendering, client.js will take the control. Let’s see what happened…

server.js

import express from 'express';
import path from 'path';
import favicon from 'serve-favicon'
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { Provider } from 'react-redux';
import reducers from './Reducers';
import ReduxThunk from 'redux-thunk'
import {
createStore,
combineReducers,
applyMiddleware,
compose
} from 'redux'
import bodyParser from 'body-parser'
import { StaticRouter } from 'react-router';
import Html from './Html';
import App from './App';
import StyleContext from 'isomorphic-style-loader/StyleContext';
const app = express();const port = process.env.PORT || 3000
app.set('host', process.env.HOST || '0.0.0.0')
app.set('trust proxy', true);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app
.use(express.static(path.join(__dirname, '../build')))
.use(favicon(path.join(__dirname, '../public', 'favicon.ico')))
app.get('/*', async (req, res, next) => {
const scripts = ['../vendor.js', '../client.js'];
const context = {};
const css = new Set() // CSS for all rendered React components
const insertCss = (...styles) => styles.forEach(style => css.add(style._getCss()))
const reducer = combineReducers(reducers)
const enhancer = compose(
applyMiddleware(ReduxThunk))
const store = createStore(reducer, enhancer);
const preloadedState = store.getState();
const appMarkup = ReactDOMServer.renderToString(
<StyleContext.Provider value={{ insertCss }}>
<Provider store={store}>
<StaticRouter context={context} location={req.url}>
<App res={res} />
</StaticRouter>
</Provider >
</StyleContext.Provider>
);const html = ReactDOMServer.renderToStaticMarkup(
<Html
children={appMarkup}
scripts={scripts}
css={css}
preloadedState={preloadedState}
/>
);
res.send(`<!doctype html>${html}`);
});
app.listen(port, () => console.log('Listening on localhost:' + port));

Html.js

import React from 'react';const Html = ({ children, scripts, css, preloadedState }) => (
<html>
<head>
<meta charSet="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#000000" />
<title>Rexpack</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" />
<link href="https://use.fontawesome.com/releases/v5.14.0/css/svg-with-js.css" rel="stylesheet" />
<style type="text/css">{[...css].join('')}</style>
</head>
<body>
<div
id="root"
dangerouslySetInnerHTML={{ __html: children }}
/>
{preloadedState && (
<script
dangerouslySetInnerHTML={{
__html: `window.__PRELOADED_STATE__=${JSON.stringify(preloadedState).replace(
/</g,
'\\u003c')}`
}}
/>
)}
{scripts.map((item, index) => <script async key={index} src={item} />)}
</body>
</html>
);
export default Html;

client.js

import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import store from './store';
import StyleContext from 'isomorphic-style-loader/StyleContext';
const insertCss = (...styles) => {
const removeCss = styles.map(style => style._insertCss())
return () => removeCss.forEach(dispose => dispose())
}
ReactDOM.render(
<StyleContext.Provider value={{ insertCss }}>
<Provider store={store} >
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
</StyleContext.Provider>,
document.getElementById('root')
)

In this article, i have tried to explain and show how to create SSR React App with ExpressJS. In my next articles, i will try to explain more and go deeper. If you follow me, i would be appreciated. Also, you can follow me on github. I leave my repository link below for you to analyze deeply. If you have any suggestion, i would be happy to hear from you. See you…

--

--

Oğuzhan Kurtuluş
0 Followers

Javascript & React & MongoDB Enthusiast. I develop application while i develop myself.