Complete Source Code for server side rendering in ReactJS using material-ui and redux is explained step by step here
You can also see details about each code segment and download source code from github here
Perplexity is the beginning of Knowledge
Complete Source Code for server side rendering in ReactJS using material-ui and redux is explained step by step here
You can also see details about each code segment and download source code from github here
Server side rendering is normally rendering the requested page at server side of client and sending back the whole html page from server.
i.e: Server-side rendering is all about handling the initial render whenever a request hits the server. For more on basics of SSR please read my first article
Let us see how to implement server side rendering in ReactJs
React: 16.7.0
Material Ui Library from Google: v3.9.3
redux: 4.0.1
redux-thunk: 4.0.1
express: 4.17.1
react-jss: 8.6.1
react-router-dom: 4.3.1
react-document-meta: 2.1.2
axios: 0.19.0
@babel/plugin-proposal-class-properties: 7.4.0
@babel/preset-env: 7.4.3
@babel/register: 7.4.4
babel-preset-es2015: 6.24.1
npm install -g create-react-app
Create a project by create-react-app as follows
create-react-app ssr-react-16
npm install @material-ui/core npm install react-redux npm install redux npm install redux-thunk npm install express npm install react-router-dom npm install react-document-meta npm install react-jss npm install axios npm install @babel/plugin-proposal-class-properties npm install @babel/preset-env npm install @babel/register npm install babel-preset-es2015
Thats it now your app is ready with library setup
Step3: babel configs for react server side app
Create a file called .babelrc in root directory of the app and paste the below code.
Babel is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments. For more you can read the babel doc.
{ "presets": ["@babel/env", "@babel/preset-react"], "plugins": [ "@babel/plugin-proposal-class-properties" ] }
Step4:
Create a file called www under bin folder which is in root directory
In this file we will be creating a server for client and allocate a port (I am using 5000 here)
Copy the code below to file just created
const app = require('../src/client/src/ssr/clientServer'); const http = require('http'); const port = normalizePort(process.env.PORT || 5000); app.set('port', port); const server = http.createServer(app); server.listen(port); function normalizePort(val) { const port = parseInt(val, 10); if (isNaN(port)) { // named pipe return val; } if (port >= 0) { // port number return port; } return false; }
In the above code snippet, we are importing a file called clientServer.js that’s covered in the next step.
require("@babel/register")({ presets: ["@babel/preset-env"] }); const express = require('express'); const path = require('path'); const requestHandler = require('./requestHandler'); const app = express(); //static files nd build file reference app.use(express.static(path.join(__dirname, '../../../../build'))); app.use(express.static(path.join(__dirname, 'public'))); app.set('view engine', 'ejs'); app.set('views', path.join(__dirname, 'public')); //for server side rendering app.use(requestHandler); // catch 404 and forward to error handler app.use((req, res, next) => { const err = new Error('Notdff Found'); err.status = 404; next(err); }); module.exports = app;
In the above code you can see a requestHandler file, which is very important file and will see there.
Create a file requestHandler.js Copy the below code.
'use strict'; import React from 'react'; import { Provider } from 'react-redux'; import thunk from 'redux-thunk'; import { applyMiddleware, createStore } from 'redux'; import { renderToString } from 'react-dom/server'; import { SheetsRegistry } from 'jss'; import JssProvider from 'react-jss/lib/JssProvider'; import path from 'path' import fs from 'fs' import { MuiThemeProvider, createMuiTheme, createGenerateClassName, } from '@material-ui/core/styles'; import green from '@material-ui/core/colors/green'; import red from '@material-ui/core/colors/red'; import { StaticRouter, matchPath } from 'react-router-dom'; import DocumentMeta from 'react-document-meta'; import reducers from '../reducers/index'; import routes from '../routes'; import routesConfigs from './routesConfig'; const middleware = applyMiddleware(thunk); function renderView(req, res, state) { // Create a theme instance. const theme = createMuiTheme({ palette: { primary: green, accent: red, type: 'light', }, }); // STEP-1 CREATE A REDUX STORE ON THE SERVER const store = createStore(reducers, state, middleware); const sheetsRegistry = new SheetsRegistry(); // Create a sheetsManager instance. const sheetsManager = new Map(); const generateClassName = createGenerateClassName(); // STEP-2 GET INITIAL STATE FROM THE STORE const initialState = JSON.stringify(store.getState()).replace(/<\/script/g, '<\\/script').replace(/<!--/g, '<\\!--'); // STEP-3 IMPLEMENT REACT-ROUTER ON THE SERVER TO INTERCEPT CLIENT REQUESTs AND DEFINE WHAT TO DO WITH THEM const context = {}; const reactComponent = renderToString( <JssProvider registry={sheetsRegistry} generateClassName={generateClassName}> <MuiThemeProvider theme={theme} sheetsManager={sheetsManager}> <Provider store={store}> <StaticRouter location={req.url} context={context}> {routes} </StaticRouter> </Provider> </MuiThemeProvider> </JssProvider> ); const css = sheetsRegistry.toString() const reactMetaComponent = DocumentMeta.renderToStaticMarkup(); if (context.url) { // can use the `context.status` that // we added in RedirectWithStatus redirect(context.status, context.url); } else { //https://crypt.codemancers.com/posts/2016-09-16-react-server-side-rendering/ //res.status(200).render('index', { reactComponent, reactMetaComponent, initialState }); fs.readFile(path.resolve('build/index.html'), 'utf8', (err, data) => { if (err) { return res.status(500).send('An error occurred') } const replacedData = data.replace( '<div id="root"></div>', `<div id="root">${reactComponent}</div> <style id="jss-server-side">${css}</style> <script> window.INITIAL_STATE = ${initialState} </script>` ); const replacedMetaTagData = replacedData .replace(`<meta id="reactMetaTags"/>`, `${reactMetaComponent}`); res.send(replacedMetaTagData); }) } } function handleRender(req, res) { const components = routesConfigs .filter(route => matchPath(req.path, route)) // filter matching paths .map(route => route.component); // check if components have data requirement let promiseObj = null; if (components.length > 0 && (components[0].fetchData instanceof Function)) { /* fetchData is the function defined in each component and make it like class function and it will be called at server side */ components[0] .fetchData(req.query) .then((response) => { renderView(req, res, response); }) .catch((error) => { console.log('***--- handleRender error ', error); renderView(req, res, {}); }); } else { renderView(req, res, {}); } } module.exports = handleRender;
import LandingComponent from '../pages/landing/components/LandingComponent'; import AboutComponent from '../pages/about/components/AboutComponent'; export default [ { path: "/", component: LandingComponent, exact: true, }, { path: '/about', component: AboutComponent, exact: true, } ];
const theme = createMuiTheme({ palette: { primary: green, accent: red, type: 'light', }, }); // STEP-1 CREATE A REDUX STORE ON THE SERVER const store = createStore(reducers, state, middleware); const sheetsRegistry = new SheetsRegistry(); // Create a sheetsManager instance. const sheetsManager = new Map(); const generateClassName = createGenerateClassName();
const replacedData = data.replace( '<div id="root"></div>', `<div id="root">${reactComponent}</div> <style id="jss-server-side">${css}</style> <script> window.INITIAL_STATE = ${initialState} </script>` );
where in index.html of public folder should compulsory has this tag
<div id="root"></div>
const replacedMetaTagData = replacedData .replace(`<meta id="reactMetaTags"/>`, `${reactMetaComponent}`);
Where in index.html of public folder, meta tag with id is should be unchanged
<meta id="reactMetaTags"/>
res.send(replacedMetaTagData);
Step 7
AboutComponent.fetchData = fetchAboutData; export default connect(mapStateToProps, null)(AboutComponent);
import axios from 'axios'; export const fetchAboutData = (params) => { return new Promise((resolve, reject) => { const url = 'https://jsonplaceholder.typicode.com/posts'; axios .get(url) .then(response => { resolve({ about: { posts: response.data } }); }) .catch((error) => { console.log('Error while fetching posts from network', error); reject(null); }); }); }
resolve({ about: { posts: response.data } });
In about reducer
const INITIAL_STATE = { posts: [] };
In about component
const mapStateToProps = (state) => { return { posts:state.about.posts }; }
getAllPosts = () => { const postView = this.props.posts.map(post => <li>{post.title}</li> ); return postView; }
Step8 :
Update the package.json start script to point to server side rendering beginning of the file, thats www under bin directory. Now server side rendering is really ready.
"scripts": { "start": "node bin.www",
Step9 : Running project:
npm run build
npm run start
OR
nodemon
OR
node bin/www
The output picture is shown below
For View Tutorial on Server Side Rendering using ReactJS, Material-ui and Redux with explanation of each code snippet click here
An Example Source Code is in Github project Link : https://github.com/pilleanand/ssr-react16/tree/master/ssr-react-16
Common issues faced in React SSR Applications
Posted by:
Anand S,
Software Engineer, Soczen Technologies Pvt Ltd,
Developer: https://sociallygood.com