
Before implementing Client-Side Rendering (CSR) or Server-Side Rendering (SSR) in a web application, it’s important to understand what these terms mean and how they impact content presentation and performance.
Server-Side Rendering (SSR)
Server-Side Rendering refers to a method where the server generates the complete HTML for a web page before sending it to the browser. The server handles the logic and builds the structure of the page, delivering a fully rendered HTML document to the user.
SSR Workflow:
1. The browser sends a request to the server for a specific page.
2.The server fetches the necessary data and inserts it into an HTML template.
3.It then generates and returns the complete HTML with the rendered UI.
4.The browser displays the page immediately—no need to run JavaScript initially.
5.After the initial load, client-side JavaScript may take over for further interactions and updates.

Client-Side Rendering (CSR)
In Client-Side Rendering, the HTML content and UI are generated directly in the browser using JavaScript. When a user requests a page, the server sends a minimal HTML file, and the client-side JavaScript handles fetching the data and rendering the interface.
CSR Workflow:
1.The server delivers a basic HTML file with references to JavaScript and CSS files.
2.The browser loads this HTML and begins downloading the CSS and JavaScript resources.
3.The JavaScript code is executed to fetch data and build the UI components.
4.The complete page, including text, images, buttons, and styles, is rendered in the browser.
5.Interactions such as form submissions and button clicks are handled by JavaScript, which updates the DOM dynamically.

JavaScript Dependency
- CSR requires JavaScript to function. If a user’s browser has JavaScript disabled, they may only see a blank or incomplete page.
- SSR does not rely on JavaScript for the initial page load, making it more accessible and providing a basic experience even if JavaScript is turned off.
Now, let’s explore how to implement both CSR and SSR in a React app. The latest version of React offers a built-in template that supports SSR out of the box.
Below is the code example demonstrating this setup:
Implementing SSR in React with Vite
- Setup your react project using vite
npm create vite@latest
- Setup your project name
? Project name: vite-project //provide your project name
3. Select the framework
>Vanilla
4. Select a variant
?Select a variant:
>create-vite-extra //
5. Select a template
?Select a template:
>ssr-vanilla
6. Now you’re given another option to choose the variants, select a variant
?Select a variant:
>Javascript
Now we have successfully set up the react project with Vite. We do not have to create the configuration ourselves, vite provides that for us as well. The codebase contains the preconfigured entry-client.tsx, entry-server.tsx and server.js.
entry-client.tsx code
import './index.css'
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
ReactDOM.hydrateRoot(
document.getElementById('root') as HTMLElement,
<React.StrictMode>
<App />
</React.StrictMode>
)
entry-server.tsx code
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import App from './App'
export function render() {
const html = ReactDOMServer.renderToString(
<React.StrictMode>
<App />
</React.StrictMode>
)
return { html }
}
server.js code
import fs from 'node:fs/promises'
import express from 'express'
// Constants
const isProduction = process.env.NODE_ENV === 'production'
const port = process.env.PORT || 5173
const base = process.env.BASE || '/'
// Cached production assets
const templateHtml = isProduction
? await fs.readFile('./dist/client/index.html', 'utf-8')
: ''
const ssrManifest = isProduction
? await fs.readFile('./dist/client/.vite/ssr-manifest.json', 'utf-8')
: undefined
// Create http server
const app = express()
// Add Vite or respective production middlewares
let vite
if (!isProduction) {
const { createServer } = await import('vite')
vite = await createServer({
server: { middlewareMode: true },
appType: 'custom',
base
})
app.use(vite.middlewares)
} else {
const compression = (await import('compression')).default
const sirv = (await import('sirv')).default
app.use(compression())
app.use(base, sirv('./dist/client', { extensions: [] }))
}
// Serve HTML
app.use('*', async (req, res) => {
try {
const url = req.originalUrl.replace(base, '')
let template
let render
if (!isProduction) {
// Always read fresh template in development
template = await fs.readFile('./index.html', 'utf-8')
template = await vite.transformIndexHtml(url, template)
render = (await vite.ssrLoadModule('/src/entry-server.tsx')).render
} else {
template = templateHtml
render = (await import('./dist/server/entry-server.js')).render
}
const rendered = await render(url, ssrManifest)
const html = template
.replace(`<!--app-head-->`, rendered.head ?? '')
.replace(`<!--app-html-->`, rendered.html ?? '')
res.status(200).set({ 'Content-Type': 'text/html' }).send(html)
} catch (e) {
vite?.ssrFixStacktrace(e)
console.log(e.stack)
res.status(500).end(e.stack)
}
})
// Start http server
app.listen(port, () => {
console.log(`Server started at http://localhost:${port}`)
})
For rendering the react app on the server, we first have to create the build for both the client and server. This is the build script.
npm run build //creates the build
npm run build:client //creates the build for client side
npm run build:server //creates the build for server side
npm run dev //starts the application
Conclusion
For React developers, mastering both CSR and SSR is key to building performant and scalable apps. CSR enables rich client-side interactivity, while SSR improves initial load and SEO. With React’s latest SSR tooling, you can easily implement either—or both—based on your project’s needs.