Server-Side Rendering (SSR) within React is one technique that enables the building of dynamic, high-performance web applications. With this feature, developers can build interactive user interfaces without depending entirely on client-side JavaScript execution. This means you can use modern backend technologies like Node.js, introduced in May 2009, to efficiently deliver content to the frontend by including server-rendered components in your application.
This approach enhances page loading times and gives developers greater control over what is displayed during each page request. In addition, it easily integrates features like state management, making it a natural fit when working with React.
In this guide, we will elaborate on the advantages of Server-Side Rendering in React, examine its underlying mechanics, and walk you through step-by-step how it can be applied to your projects. We also highlight challenges associated with SSR, such as performance optimization and maintenance, and share some tips that are very practical to get started with server-side rendering today.
What Is Server-Side Rendering?
Server-side rendering is the process of rendering web pages on the server before delivering them to the client’s browser. This leads to faster page loads, better performance, and improved SEO for React applications. It is particularly useful for users with slower internet connections or devices with limited memory and processing power because the initial rendering happens on the server.
SSR in React significantly minimizes page load times by eliminating unnecessary back-and-forth communication between the client and the server. Apart from this, it increases SERP visibility, overcoming the limitations of CSR, where websites that are dominated by JavaScript fail to get indexed properly by the search engine.
SSR also guarantees consistent rendering across various browsers. Although CSR-based modern web development is predominantly based on some browser-specific API or custom functionality, it cannot be relied on to work properly by itself. The server-side SSR pre-renders components beforehand, thus providing compatibility across various platforms.
Advantages of SSR | Disadvantages of SSR |
---|---|
The initial page load is faster: All the needed data is rendered on the server side, meaning that the client receives a completely rendered page. |
Additional Infrastructure: Implementing SSR requires more resources and effort compared to standard client-side rendering. |
Improved SEO: Search engine crawlers can easily index content, boosting visibility on platforms like Google and Bing. |
Increased Complexity: SSR introduces layers of server-side logic, making development and maintenance more complex. |
Reduced Lag Times: Pre-rendering eliminates delays caused by waiting for components to load on the client side. |
Performance Limitations: While SSR improves web performance, it cannot match the speed of native mobile apps, which operate entirely offline. |
Implementing Server-Side Rendering in React
Let’s explore how to implement SSR in React, focusing on its application in frameworks like Next.js and Express.js. We’ll use an example of an e-commerce website to demonstrate SSR’s effectiveness.
Use Case: E-commerce Website
An e-commerce site will have a number of thousands of dynamic pages that need updating quite frequently for example, such as a listing page or a category page, and these have to be search friendly for both the users as well as for the search engines.
With the use of SSR using frameworks like Next.js or Express.js, you can create the HTML markup on the server for every page. The content will thus be SEO-friendly and provides a smooth user experience.
Server-Side Rendering Implementation by Next.js
Let’s get into server-side rendering using an example from an e-commerce website with Next.js. I am here to assist you in building and deploying an application step-by-step powered by server-side rendering in this tutorial.
Step 1: New Project in Next.js
Begin your journey with this command by initiating a new project in your terminal:
npx create-next-app my-ecommerce-app cd my-ecommerce-app
This will create a new Next.js application in the my-ecommerce-app directory.
Step 2: Install Dependencies
Install necessary dependencies for your project:
npm install react react-dom next
Once installed, your package.json file should look like this:
{ "name": "my-ecommerce-app", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { "@next/font": "13.1.6", "eslint": "8.34.0", "eslint-config-next": "13.1.6", "next": "13.1.6", "react": "18.2.0", "react-dom": "18.2.0" } }
Note the versions of the packages used here, as they were current at the time this guide was written.
Step 3: Set Up Environment Configuration
Next, define environment variables in a .env.local file. This file will store settings for various environments (e.g., development, staging, production).
Create a .env.local file in the root directory and add the following:
API_URL=http://localhost:3000
To protect sensitive information (e.g., API keys or database credentials), avoid committing .env.local to source control. Instead, create a .env.example file with placeholder values that other developers can use as a template.
Step 4: Create a New Page
Next.js uses a file-based routing system. Each file in the pages directory represents a page in your application.
For example, to create a product listing page at /products, add a new file at pages/products/index.js with the following code:
import { useState, useEffect } from 'react'; function ProductsPage() { const [products, setProducts] = useState([]); useEffect(() => { async function fetchProducts() { const res = await fetch('/api/products'); const products = await res.json(); setProducts(products); } fetchProducts(); />, []); return ( <div> <h1>Products</h1> <ul> {products.map((product) => ( <li key={product.id}>{product.name}</li> )) </div> ); } export default ProductsPage;
This component fetches a list of products from an API and displays them in a simple list format.
Step 5: Create an API Endpoint
To serve product data, create an API endpoint using Next.js’s built-in API routing.
Create a new file at pages/api/products.js with the following code:
const products = [ { id: 1, name: 'Product 1' }, { id: 2, name: 'Product 2' }, { id: 3, name: 'Product 3' }, ]; export default function handler(req, res) { res.status(200).json(products); }
This API endpoint returns a mock list of products when accessed.
Step 6: Update the Page to Use Server-Side Rendering
By default, Next.js does server-side rendering. To have it render SSR, edit pages/products/index.js so it utilizes the getServerSideProps function:
function ProductsPage({ products }) { return ( <div> <h1>Products</h1> <ul> {products.map((product) => ( <li key={product.id}>{product.name}</li> ))} </ul> </div> ); } export async function getServerSideProps() { const res = await fetch(`${process.env.API_URL}/api/products`); const products = await res.json(); return { props: { products } }; } export default ProductsPage;
In this updated version, the product data is fetched on the server using getServerSideProps and passed to the component as props.
Step 7: Start the Development Server
Run the development server with:
npm run dev
This will start a local server at http://localhost:3000 .
Step 8: Test the Application
Open your browser and go to the /products page. You should see a list of products displayed on the page.
To confirm SSR is working, view the page source in your browser. You will notice the product data was included in the HTML markup meaning it was rendered on the server.
Congratulations
You have just implemented server-side rendering in a Next.js application. SSR is a powerful tool for building SEO-friendly and high-performing web applications. Using SSR with the robust framework in Next.js gives you the capacity to deliver the best experience possible for your users.
Implementing Server-Side Rendering Using Express.js
Now, let’s explore how to implement server-side rendering (SSR) for the same use case in an Express.js application. Follow these steps to build a fully functional SSR-enabled Express.js application.
Step 1: Create a New Express.js Application
Start by creating a new directory for your project. Inside this directory, initialize a new project with:
npm init
This command generates a package.json file in your project directory. Next, install Express.js and the required dependencies:
npm install express react react-dom next
Here’s how the package.json file will look:
{ "name": "express-demo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "dependencies": { "express": "^4.18.2", "next": "^13.1.6", "react": "^18.2.0", "react-dom": "^18.2.0" } }
Step 2: Set Up Environment Configuration
Define environment variables in a .env.local file. This file stores configuration settings for various environments (e.g., development, staging, production).
Create a .env.local file in your project root with the following content:
API_URL=http://localhost:3000
For security, exclude .env.local from source control and create a .env.example file with placeholder values for sharing configuration templates with other developers.
Step 3: Set Up the Server
Create a server.js file in the project root to set up the Express.js server. Add the following code:
const express = require('express'); const next = require('next'); const dev = process.env.NODE_ENV !== 'production'; const app = next({ dev }); const handle = app.getRequestHandler(); app.prepare().then(() => { const server = express(); server.get('/', (req, res) => { return app.render(req, res, '/home'); }); server.get('/products', (req, res) => { return app.render(req, res, '/products'); }); server.all('*', (req, res) => { return handle(req, res); }); server.listen(3000, (err) => { if (err) throw err; console.log('> Ready on http://localhost:3000'); }); });
This server listens on port 3000 and uses the Next.js instance to render pages. Routes are defined for / and /products, while all other requests fall back to the default request handler.
Step 4: Create the Home Page
In the project’s root directory, create a pages folder. Inside it, add a home.js file with the following code:
import Link from 'next/link'; function HomePage() { return ( <div> <h1>Welcome to our e-commerce website!</h1> <Link href="/products"> <a>View our products</a> </Link> </div> ); } export default HomePage;
This code defines a simple home page with a welcome message and a link to the products page.
Step 5: Create the Products Page
In the pages directory, add a products.js file with this code:
import { useEffect, useState } from 'react'; function ProductsPage() { const [products, setProducts] = useState([]); useEffect(() => { async function fetchProducts() { const response = await fetch('/api/products'); const data = await response.json(); setProducts(data.products); } fetchProducts(); }, []); return ( <div> <h1>Products</h1> <ul> {products.map((product) => ( <li key={product.id}>{product.name}</li> ))} </ul> </div> ); } export default ProductsPage;
This component fetches and displays a list of products using the useEffect hook to manage state and perform data fetching.
Step 6: Create the API Endpoint
Add an API endpoint for products directly in the server.js file:
server.get('/api/products', (req, res) => { const products = [ { id: 1, name: 'Product 1' }, { id: 2, name: 'Product 2' }, { id: 3, name: 'Product 3' }, ]; res.status(200).json({ products }); });
This API endpoint serves a mock list of products. In real-world scenarios, you would fetch this data from a database or other data source.
Here’s the updated server.js file:
const express = require('express'); const next = require('next'); const dev = process.env.NODE_ENV !== 'production'; const app = next({ dev }); const handle = app.getRequestHandler(); app.prepare().then(() => { const server = express(); server.get('/', (req, res) => { return app.render(req, res, '/home'); }); server.get('/products', (req, res) => { return app.render(req, res, '/products'); }); server.get('/api/products', (req, res) => { const products = [ { id: 1, name: 'Product 1' }, { id: 2, name: 'Product 2' }, { id: 3, name: 'Product 3' }, ]; res.status(200).json({ products }); }); server.all('*', (req, res) => { return handle(req, res); }); server.listen(3000, (err) => { if (err) throw err; console.log('> Ready on http://localhost:3000'); }); });
Step 7: Start the Server
Start the server by running the following command:
node server.js
This will start the application at http://localhost:3000 .
Step 8: Test the Application
Visit http://localhost:3000/ in your browser. You’ll see the home page with a link to the products page. Click the link to navigate to the /products page, where you’ll see the list of products fetched from the API.
Congratulations!!
You have completed implementing server-side rendering in an Express.js application. From here, with this knowledge you can create scalable and SEO friendly web applications by using both Next.js and Express.js.
Server-Side Rendering: SEO versus Performance
While server-side rendering (SSR) comes with significant advantages concerning both SEO and performance, these advantages have related trade-offs as well. When you consider such, you are equipped to determine the right direction that your project is headed by settling on the pros and cons mentioned.
Advantages for SEO
Improved Crawlability: SSR makes it easier for search engines to crawl and index web content since fully rendered HTML is sent to the client.
Enhanced Content Understanding: Since pre-rendered HTML is delivered, search engines can better understand your content, making it more visible.
Higher Rankings: The search engine may rank SSR pages higher because they have a better user experience with fast load times and complete content visibility.
JavaScript Content Visibility: Such content that could otherwise be impractical or impossible for the search engine to access will be visible in SSR pages due to visible content generated by javascript.
Performance Benefit
Faster Initial Rendering: Although CSR can sometimes allow for faster initial page loads because the basic HTML structure is rendered immediately, SSR benefits slower devices by not requiring as much client-side processing.
Reduced Client-Side Load: SSR reduces the computational load on the client, making it ideal for users with older devices or limited processing power.
Minimized Network Requests: SSR minimizes the number of network requests that need to be made to render a page, thereby reducing load times.
Improved Navigation Speeds: Once loaded, navigation on an SSR-enabled site can be faster because much of the rendering work is done ahead of time by the server.
Trade-Offs
Balancing SEO and Performance: For some, the loss is in principle negligible; for others, that may carry a lot of heavy dynamic content, it will be high.
Increased Complexity: In addition to the added layers of complexity while developing and maintaining, SSR would bring higher costs as compared to CSR.
Initial Load Delays: Though the client’s processing is greatly decreased by SSR, the initial page loads may be slower as the server will have to process and deliver fully rendered HTML, CSS, and JavaScript.
Conclusion
Server-side rendering in React provides a powerful solution for creating dynamic, high-performance web applications. By rendering pages on the server before sending them to the client, SSR enhances SEO, optimizes page load times, and improves user experiences, particularly for those with slower devices or limited processing power.
Even though SSR creates additional challenges such as higher complexity and cost to develop, new frameworks such as Next.js and Express.js simplify the implementation. With strong caching strategies and good infrastructure in place, it’s possible for SSR to boost user experience quite notably, so for sites having many dynamic pieces of content like an e-commerce website, it makes up for being an ideal solution.
For React developers, SSR is an essential tool in their arsenal. Properly implemented, it can bring substantial benefits in terms of SEO, performance, and user satisfaction.
If you’re planning to build a project with SSR and need expert assistance, consider hiring a skilled React developer who can deliver high-quality solutions tailored to your specific requirements.