Server-Side Rendering in React with Express.js in the MERN Stack
Learn how to implement server-side rendering (SSR) in React with Express.js for faster load times.
Introduction
In modern web development, performance and SEO optimization are critical factors for the success of a web application. One way to achieve these optimizations is by using server-side rendering (SSR). Server-side rendering refers to the process where the server renders the initial HTML of a React component and sends it to the browser, rather than relying on the client to render everything.
This method enhances performance because the user can see the initial content more quickly, and it also improves SEO because search engine bots can crawl pre-rendered content more effectively.
In this blog, we will explore how to implement SSR with React and Express.js in a MERN (MongoDB, Express.js, React.js, Node.js) stack application. We’ll walk through setting up Express.js to serve a React app that renders on the server before being sent to the client.
Main Content
1. Understanding Server-Side Rendering (SSR)
In a typical client-side rendered React application, the browser loads a minimal HTML document and JavaScript, which then runs to render the page. In SSR, however, the server pre-renders the content and sends it to the client as a fully formed HTML page. This results in:
Faster load times: The browser receives a fully rendered HTML page rather than waiting for JavaScript to execute.
Improved SEO: Search engines can crawl the content directly since the page is already rendered on the server.
Better performance for first-time users: SSR reduces the need for clients to download and run large JavaScript files before rendering.
2. Setting Up the MERN Stack for SSR
Step 1: Initialize a new Node.js project
Start by setting up a new Node.js project and installing the necessary dependencies.
mkdir ssr-mern-app
cd ssr-mern-app
npm init -y
Install the required dependencies:
npm install express react react-dom react-router-dom
npm install @babel/core @babel/preset-env @babel/preset-react babel-loader webpack webpack-cli webpack-node-externals
Express: Server-side framework.
React: Frontend library for building the user interface.
react-router-dom: To handle routing in the React app.
Babel and Webpack: Used for transpiling and bundling the React code.
webpack-node-externals: Prevents Webpack from bundling server-side dependencies.
Step 2: Setting Up Webpack for SSR
Create a webpack.server.js
file in the root of the project for server-side bundling:
const path = require('path');
const nodeExternals = require('webpack-node-externals');
module.exports = {
target: 'node',
entry: './src/server.js',
output: {
path: path.resolve(__dirname, 'build'),
filename: 'server.bundle.js',
},
externals: [nodeExternals()],
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
},
},
},
],
},
};
Explanation of Code:
Webpack Target: Set to
node
because we’re building for server-side code.Entry Point:
server.js
is where the server-side rendering logic will be implemented.Output: The compiled server bundle is saved in the
build
directory.Babel Loader: Handles JavaScript transpiling for compatibility with older browsers.
Step 3: Setting Up the React Client-Side App
Create a React component, App.js
, that you want to render:
import React from 'react';
const App = () => {
return (
<div>
<h1>Hello from SSR React!</h1>
<p>This page was rendered on the server.</p>
</div>
);
};
export default App;
Explanation of Code:
- React Component: A simple React component is created with some text to display.
3. Setting Up the Express.js Server to Render React
Step 1: Create a Server File
Create a server.js
file in the src
folder to handle server-side rendering:
import express from 'express';
import path from 'path';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import App from './App';
const app = express();
// Serve static assets from the 'public' folder
app.use(express.static(path.resolve('public')));
app.get('*', (req, res) => {
// Render the React component to a string
const appString = ReactDOMServer.renderToString(<App />);
// Return the full HTML page
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>SSR with React and Express</title>
</head>
<body>
<div id="root">${appString}</div>
</body>
</html>
`);
});
app.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});
Explanation of Code:
Express Setup: Sets up the Express server.
ReactDOMServer:
renderToString
renders theApp
component to an HTML string.Serving HTML: The HTML is returned to the client with the rendered React component.
Static Assets: The
express.static
middleware serves static assets like CSS and JS.
Step 2: Set Up React Hydration for Client-Side Rendering
To enable client-side interactivity after the SSR content is loaded, you need to "hydrate" the React app. Create a client.js
file:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.hydrate(<App />, document.getElementById('root'));
Explanation of Code:
- Hydration: React's
hydrate
method attaches event listeners to the pre-rendered HTML, making the app interactive without re-rendering the entire page.
Step 3: Update Webpack for Client-Side Bundling
Create a webpack.client.js
file for bundling the client-side code:
const path = require('path');
module.exports = {
entry: './src/client.js',
output: {
path: path.resolve(__dirname, 'public'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
},
},
},
],
},
};
4. Best Practices for Server-Side Rendering
Data Fetching: When implementing SSR, consider how you will handle data fetching. One approach is to fetch data on the server side before rendering the React app, then pass it down as props to the components.
Caching: Use caching techniques for frequently requested pages to improve performance and reduce server load.
Error Handling: Ensure proper error handling during SSR to avoid issues that might break the rendering process on the server.
SEO Optimization: Utilize meta tags, structured data, and proper header tags to improve SEO when rendering on the server.
Conclusion
Server-Side Rendering (SSR) with React and Express.js offers numerous benefits, such as improved performance and SEO. By setting up SSR in a MERN stack app, we can ensure faster load times and better search engine optimization for React applications. Implementing SSR might initially seem complex, but with proper setup and configuration, you can create efficient and scalable web apps that perform well across various devices and browsers.
Now that you understand how SSR works in the MERN stack, try implementing SSR in your own project. If you have any questions or need further assistance, don’t hesitate to ask in the comments below or reach out for help!
References/Resources
*Image designed by Freepik