How to Implement Client-Side Rendering in React

This article provides a straightforward guide on how to implement Client-Side Rendering (CSR) in React. You will learn the core mechanics of CSR, how to set up a standard React application that renders entirely in the browser, and how to manage key aspects of client-side apps, such as routing and data fetching.

Understanding Client-Side Rendering in React

In Client-Side Rendering (CSR), the server sends a bare-minimum HTML document to the browser, usually containing just a single <div> container and a link to a JavaScript bundle. The browser downloads this bundle, and React executes to generate the DOM nodes, handle user interactions, and fetch data dynamically.

By default, standard React applications built with bundlers like Vite or Create React App use CSR.

Step 1: Set Up a React Project

The fastest way to implement a CSR React application is by using Vite, which sets up a highly optimized development environment.

Run the following commands in your terminal:

npm create vite@latest my-csr-app -- --template react
cd my-csr-app
npm install
npm run dev

This creates a pre-configured React project designed for client-side rendering.

Step 2: Analyze the HTML and Entry Points

Open the project in your code editor. In a CSR architecture, the application relies on two main files to render:

  1. index.html: Located in the root folder. It contains a minimal structure:

    <!doctype html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <title>React CSR App</title>
      </head>
      <body>
        <div id="root"></div>
        <script type="module" src="/src/main.jsx"></script>
      </body>
    </html>
  2. src/main.jsx: This is the entry point where React mounts your component tree onto the HTML DOM container.

    import React from 'react'
    import ReactDOM from 'react-dom/client'
    import App from './App.jsx'
    
    ReactDOM.createRoot(document.getElementById('root')).render(
      <React.StrictMode>
        <App />
      </React.StrictMode>,
    )

When a user visits your site, they receive this empty index.html. The browser then executes the main.jsx bundle to inject the <App /> component into the <div id="root"> element.

Step 3: Implement Client-Side Routing

Because CSR apps run entirely in the browser, navigating to a new page must not trigger a full server refresh. Instead, you use a client-side router to update the URL and swap components dynamically.

Install React Router:

npm install react-router-dom

Configure the router in src/App.jsx:

import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';

function Home() {
  return <h1>Home Page</h1>;
}

def About() {
  return <h1>About Page</h1>;
}

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Home</Link> | <Link to="/about">About</Link>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

This setup intercepts browser navigation events and updates the view immediately without requesting new HTML pages from the server.

Step 4: Handle Data Fetching

In a CSR application, data is fetched after the initial page has loaded. This is typically handled within the component lifecycle using the useEffect hook.

Here is an example of fetching data from an API inside a CSR component:

import { useState, useEffect } from 'react';

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/users')
      .then((response) => response.json())
      .then((data) => {
        setUsers(data);
        setLoading(false);
      });
  }, []);

  if (loading) {
    return <p>Loading data...</p>;
  }

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

export default UserList;

When this component mounts, it displays a loading state, fetches the data on the client side, and updates the UI once the data is ready.