How to Partly Server-side Render in React
Apr 22, 2020 • 8 Minute Read
Introduction
Server-side code works well with React and other frontend frameworks, allowing even a pure frontend application to effectively utilize the benefit of server-side rendering using a few simple tools.
In this guide, we'll take a look at server-side rendering and how it can be used to greatly improve the performance of your React application.
Server-side Rendering
Server-side rendering (SSR) renders a SPA (single-page application) on the server and subsequently sends it across to the client. The client then interprets the pages and displays them on the frontend, after which control of the application is taken over by the framework.
An important performance booster contributed by SSR is the quick initial loading of the application. You've probably noticed that spinning the local development server through any framework's CLI takes a long time to render the application for the first time. SSR sends down a fully loaded app from the server itself so the computing power of the server compensates for the processing power used by the client. As servers are considerably powerful, the bundles of HTML pages are sent across very quickly. Along with gaining speed, your application becomes visible to crawl bots for search engine optimization.
Implementation in React
Setting up
Make sure you have Nodejs and npm installed on your machine (at least version 8 or higher) along with a code editor and a web browser (preferably Chrome or Firefox).
Create a new project using create-react-app:
npx create-react-app react-ssr-app
Clean up the project template by removing logo.svg, all its relevant imports, and some boilerplate inside App.js. Your App.js should look like this :
import React from 'react';
const App = () => {
return (
<div className="app">
<h2>hello from app!</h2>
</div>
);
}
export default App;
Run npm start to spin a local development server. You can see the app up and running on localhost:3000 in your browser (create-react-app automatically does this for you) with a simple message: **hello from app! ** Now the next step is to make changes in this application so the same app is rendered from the server.
Replacing render() with hydrate()
Replace the render() method with hydrate() inside index.js .
//...
ReactDOM.hydrate(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
...//
Hydration ensures that all the functionality remains intact by attaching event listeners to the already existing markup so search engine bots can index the markup preloaded from the server.
Installing Libraries and Packages
Express is a web framework built on top of Nodejs to provide HTTP utility methods and middleware and allow a server to be built with only a few lines of code.
npm install express
Next, install babel to transpile our local JSX code and ignore-styles to ignore imported styles in our node environment.
npm install @babel/register @babel/preset-env @babel/preset-react ignore-styles
Structuring Server-side Code
Create a folder named server inside the root directory with two files inside it: index.js and server.js .
The latter will contain all the code for the server, and the former will be treated as the entry point.
react-ssr-app
├── src -- src folder
└── server -- server folder
├── server.js -- server-side rendering code
├── index.js -- entry point for the server
Creating the Server
Server set up is done in the node environment using express. Select a port (could be any port as long as no process is running on that port already) and call express() to initialize an express application. Use the router method to set up a simple route and tell the app to listen to that port.
const express=require('express');
const PORT = 8080;
const app = express();
const router = express.Router()
router.use('^/$', serverRenderer)
app.use(router)
app.listen(PORT, () => {
console.log(`react app running on port ${PORT}`)
})
Rendering App.js from the Server
The next step is to render the App.js file from the server, so import the file. To handle all file manipulations, require the built-in file system module (fs) and path module, and to render a React application, require React and ReactDOMServer.
Create a simple function that takes in three arguments: req, res and next. The goal is to create a static HTML file inside a build folder and serve that file on the server using the express application. Next, invoke the renderToString() method on ReactDOMServer to render the entire app.js as a string on the server at the created route using router.use().
const path=require('path');
const fs=require('fs');
const express=require('express');
const React=require('react');
const ReactDOMServer=require('react-dom/server');
import App from '../src/App'
const PORT = 8080
const app = express()
const router = express.Router()
const serverRenderer = (req, res, next) => {
fs.readFile(path.resolve('./build/index.html'), 'utf8', (err, data) => {
if (err) {
console.error(err)
return res.status(500).send('An error occurred')
}
return res.send(
data.replace(
'<div id="root"></div>',
`<div id="root">${ReactDOMServer.renderToString(<App />)}</div>`
)
)
})
}
router.use('^/$', serverRenderer)
router.use(
express.static(path.resolve(__dirname, '..', 'build'))
)
app.use(router)
app.listen(PORT, () => {
console.log(`SSR running on port ${PORT}`)
})
Handling Entry Point
The application is not rendering any styles from the server, so require ignore-styles along with babel to ensure our imports inside Nodejs work as required, and code transpilation will happen without any issues. Finally, require the server.js file since the node application will run this file to execute all the server-side code.
require('ignore-styles')
require('@babel/register')({
ignore: [/(node_modules)/],
presets: ['@babel/preset-env', '@babel/preset-react']
})
require('./server')
Testing
Here, you will test server you just created. You won't run the frontend, so cancel all processes initiated by npm start by killing that terminal.
Since you are serving the index.html file inside the build folder, first you need to create that build folder. Inside the root directory, run the following command :
npm run build
You will see a build folder created with an index.html file inside it. Now run the node application with the following command :
node server/index.js
You will see the following message on the console first :
SSR running on port 8080
Now run localhost:8080 in your browser to see our app.js file rendered on the browser.
If you get any depreciated warnings or errors, you can use the exact version of these libraries used in this guide by updating your package.json file and running the command npm i .
{
..//
"dependencies": {
"@babel/preset-env": "^7.6.3",
"@babel/preset-react": "^7.6.3",
"@babel/register": "^7.6.2",
"express": "^4.17.1",
"ignore-styles": "^5.0.1",
"isomorphic-fetch": "^2.2.1",
"react": "^16.11.0",
"react-dom": "^16.11.0",
"react-router-dom": "^5.1.2",
"react-scripts": "3.2.0",
"serialize-javascript": "^2.1.0"
}
..//
}
Conclusion
Partly rendering your application through the server side has several benefits, and it’s important to understand when and where to use it. If you have a landing page for your company with loads of images and animations, server-side rendering can quickly render that page for users and let client-side JavaScript handle the rest of your application thereafter. In addition, your website will be visible to crawl bots for SEO to induce more traffic on your website. However, the performance of server-side rendering depends largely on the server, and therefore rendering your whole application that way could increase the complexity of writing down the server-side code.