Skip to main content

Command Palette

Search for a command to run...

SSR with React JS

Updated
5 min read

This short blog is for beginners who want to learn SSR with React JS. To start with we need to have basic understanding of React JS and Webpack bundler. Install Node JS in your system and create a folder in vs code and we will start adding files with required details.

Use the link given below for accessing the git repo and clone the app for more clarity. Github Repository

If you want to read more about SSR using redux toolkit please follow this process mentioned on the official documentation.

You can also follow this tutorial series for more in depth implementation of SSR with redux, router and React JS.

webpack.config.js

This file is meant to setup initial configuration for bundling the entire react app and build final output bundle. Check the below code which I have used for this setup.

const path = require('path');

module.exports = {
  mode: 'production',
  entry: {
    index: './src/index.js',
  },
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: "[name].js"
  },
  module: {
    rules: [
      { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" }
    ]
 }
}

This file (webpack.config.js) is made in a node environment. For detailed explanation of how a Webpack setup please refer to this documentation.

Mode is set in production with entry point of index.js in src folder. Output resolves to [name].js in 'build' folder. Apart from this I have set module rules as loading .js files excluding node-modules and also use babel-loader for transpiling the entire React JS code using Webpack.

Apart from this babel is added as dependency via .babelrc file

{
  "presets": ["@babel/env", "@babel/react"]
}

package.json

This file is needed for importing dependency files required by React app. Follow the code structure for more details. We can control the overall dependency structure using this file.

index.js

This file is the entry point for this entire project. As this is written in Node JS we cannot use import or export statements in it (as they are not part of Node JS or browser). Below code is starter code for this file.

require("@babel/register");
const path = require("path");
const express = require("express");

const app = express()

app.use('/build', express.static(path.resolve(__dirname, 'build')));
app.listen(8000);

app.get('/', (_, res) => {
  const { content, preloadedState } = renderHtml(initialState)
  const response = createPage(content, preloadedState)
  res.send(response);
});

function createPage(html='', preloadedState){
    return `<!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="utf-8">
      <title> Google Search - SSR  </title>
      <link rel="stylesheet" href="build/styles.css" />
    </head>
    <body>
          <div id="root" class="wrap-inner">
              ${html}
          </div>
          <script>
              window.__STATE__ = ${JSON.stringify(preloadedState) ||  ""}
          </script>
          <script src="build/index.js"></script>
    </body>
    `
}

Once this app is launched on '/' route. The role of createPage function is to accept content and some preloaded state which would be needed at later stage for initializing redux toolkit needed in our app. I have save the preloaded state in stringified version window property STATE. The response of this api call on '/' is a HTML page in stringifed, which is basically the output of the createPage function. renderHTML function is responsible for creating the HTML rendered page of react app. Detailed functionality of this function is explained further. For now you can assume that react app is initialized with a default state passed on initial call and then final react app in rendered and added in between the HTML skeleton code as mentioned in the createPage function.

React code structure

Following are the contents of src folder.

  • Components folder, which holds the component files needed throughout the react app.
  • Redux folder holds the store.js file which has the necessary code for making api calls, holding the reducer, and creating the store to be wrapped around the entire React app.
  • index.js holds the code to hydrate the overall react app and initialize the redux state with preloaded state.
  • renderHTML, this function is used to render the react app once the redux store is loaded with default state and returned this rendered app in stringified version (content) along with prelaoded state back to index.js file. For more details I have added the code snippet below.
import React from 'react'
import { renderToString } from 'react-dom/server'
import { Provider } from 'react-redux'
import createStoreFromState from './redux/store'
import App from './components/App'

module.exports = function render(initialState) {
  const store = createStoreFromState(initialState)
  let content = renderToString(<Provider store={store}><App /></Provider>);
  const preloadedState = store.getState()
  return { content, preloadedState };
}
  • When user lands on '/' route, app.get function runs and eventually renderHtml runs with a default state.
  • Using this default state render function runs and in it createStoreFromState runs with this same default state. createStoreFromState function is actually mentioned in the store.js of redux folder.
  • The purpose of this function is to create a redux store using this state and return this store back to render function. For more details on how create store works please refer to this documentation.
  • Once we have this store, we call the renderToString function from 'react-dom/server' module and get the stringified version of the entire react app rendered using this default state.
    renderToString(<Provider store={store}><App /></Provider>)
    
  • Current redux state is obtained using the store.getState() function.
  • In return we get string version of the entire react app as content and current state as preloadedState.
  • This output is obtained in the app.get function of index.js and then we create the final html response structure using the react page as content and preloadedState.

Index.js

A point to note here is that, once we have rendered the initial app via server using the default state. We still provide window.STATE = ${JSON.stringify(preloadedState) || ""} inside the script tag, so that this window property is available in the react code. This state is again needed in the src/index.js file.

<script>
       window.__STATE__ = ${JSON.stringify(preloadedState) ||  ""}
</script>

Since we need to render the entire app on server. We have to make sure that any action triggered is handled on server and necessary changes are made before sending the html app in response. Inside src/index.js, we create the store using the preloaded state and delete the STATE property. Then we use hydrate instead of render function to render the entire react app on server. This action is needed as we need to recreate the app on state change and send back the response to client.