CSR vs SSR — Client-Side Rendering Versus Server-Side Rendering Compared
In this tutorial, you will learn about CSR vs SSR. We cover key concepts, practical examples, and best practices to help you master this topic.
CSR vs SSR compares client-side and server-side rendering across performance metrics, SEO capabilities, development complexity, user experience, and use cases to help you choose the right rendering strategy.
What You'll Learn
By the end of this tutorial, you will understand the fundamental differences between CSR and SSR, how each affects Core Web Vitals, the development complexity tradeoff, when to use each approach, and how to make informed architectural decisions for your web applications.
Why It Matters
The rendering strategy you choose affects every aspect of your application: how fast it loads, how it ranks in search engines, how complex the codebase is, how much servers cost, and how users perceive performance. Choosing wrong leads to unnecessary complexity or poor performance.
Real-World Use
A SaaS analytics dashboard started as a CSR SPA. As they added public-facing content pages, SEO suffered. They migrated to Next.js with SSR for marketing pages and kept the dashboard as CSR. This hybrid approach improved organic traffic by 200 percent while maintaining dashboard performance.
Core Web Vitals: CSR vs SSR
┌──────────────────────────────────────────────────────────┐
│ CSR vs SSR — Core Web Vitals │
├──────────────────────┬───────────────────────────────────┤
│ Metric │ CSR vs SSR │
├──────────────────────┼───────────────────────────────────┤
│ │ │
│ First Contentful │ CSR: Poor (3000-5000ms) │
│ Paint (FCP) │ SSR: Good (800-1500ms) │
│ │ │
│ Largest Contentful │ CSR: Poor (4000-8000ms) │
│ Paint (LCP) │ SSR: Good (1200-2500ms) │
│ │ │
│ Time to Interactive │ CSR: Poor (5000-10000ms) │
│ (TTI) │ SSR: Good (2000-4000ms) │
│ │ │
│ First Input Delay │ CSR: Equivalent (50-100ms) │
│ (FID) │ SSR: Equivalent (50-100ms) │
│ │ │
│ Cumulative Layout │ CSR: Worse (JS loading shifts) │
│ Shift (CLS) │ SSR: Better (HTML has layout) │
│ │ │
│ Time to First Byte │ CSR: Better (100-300ms) │
│ (TTFB) │ SSR: Worse (200-800ms) │
│ │ │
└──────────────────────┴───────────────────────────────────┘
Think of CSR versus SSR like two ways to deliver a presentation. CSR is like handing everyone a projector (JavaScript) and asking them to assemble it before you start speaking. SSR is like having the slides already projected when people walk in — they see content immediately while you set up the interactive Q&A (hydrate).
Code Comparison: Same Component, Different Rendering
// Same component — rendered differently
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
// CSR: data fetched on the client (useEffect)
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(data => {
setUser(data);
setLoading(false);
});
}, [userId]);
if (loading) return <Spinner />;
return (
<div>
<h1>{user.name}</h1>
<p>{user.bio}</p>
</div>
);
}
// SSR: data fetched on the server, component receives it as props
function UserProfile({ user }) {
// No loading state needed — data is already in HTML
return (
<div>
<h1>{user.name}</h1>
<p>{user.bio}</p>
</div>
);
}
// SSR server code:
app.get('/users/:id', async (req, res) => {
const user = await api.fetchUser(req.params.id);
const html = renderToString(
React.createElement(UserProfile, { user })
);
res.send(`...${html}...<script>window.__DATA__=${JSON.stringify({user})}</script>`);
});
Development Complexity Comparison
// CSR — simpler initial setup
// index.js (entry point)
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
// vite.config.js
export default defineConfig({
plugins: [react()],
build: { outDir: 'dist' }
});
// SSR — more configuration
// server.js
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import App from './App';
const app = express();
app.use(express.static('dist'));
app.get('*', async (req, res) => {
const data = await fetchData(req.url);
const html = renderToString(<App data={data} url={req.url} />);
res.send(`<!DOCTYPE html>
<html>
<head><title>SSR App</title></head>
<body>
<div id="root">${html}</div>
<script>window.__INITIAL_DATA__=${JSON.stringify(data)}</script>
<script src="/client.js"></script>
</body>
</html>
`);
});
// Additional considerations for SSR:
// - Need to handle routing on both server and client
// - Need to serialize and pass initial data
// - Need to handle environment-specific code (window, document)
// - Need to manage server-side styles extraction
Decision Flowchart
function chooseRenderingStrategy(requirements) {
const { needsSEO, userInteraction, contentFreshness, serverBudget } = requirements;
if (needsSEO && contentFreshness === 'dynamic') {
return 'SSR'; // SEO + dynamic content = SSR
}
if (needsSEO && contentFreshness === 'static') {
return 'SSG'; // SEO + static content = SSG (simpler, faster)
}
if (!needsSEO && userInteraction === 'high') {
return 'CSR'; // No SEO + highly interactive = CSR (simpler)
}
if (!needsSEO && serverBudget === 'low') {
return 'CSR'; // No SEO + low budget = CSR (cheaper hosting)
}
return 'Hybrid'; // Mix SSR for public pages, CSR for app pages
}
// Expected outputs:
// Blog with SEO: SSR or SSG
// Admin dashboard with no SEO: CSR
// E-commerce with SEO and user accounts: SSR or Hybrid
// Real-time chat app: CSR with SSR for initial load
Common Mistakes
- Using CSR for SEO-critical pages. Search engines may not execute JavaScript. CSR pages risk not being indexed. Always use SSR or SSG for content that needs search visibility.
- Using SSR for everything. SSR adds server cost and complexity. Use CSR for authenticated sections, admin panels, and internal tools where SEO is not needed.
- Not measuring performance. The only way to know if SSR is helping is to measure. Compare FCP, LCP, TTFB, and TTI between CSR and SSR implementations.
- Ignoring the hydration cost. SSR sends HTML quickly but the page is not interactive until JavaScript loads and hydrates. Users may try to click before hydration completes.
- Framework lock-in without evaluation. Next.js is great for SSR, but it locks you into React. Consider whether CSR with a lighter framework would meet your needs.
Practice Questions
- How does CSR affect First Contentful Paint compared to SSR?
- Why does SSR have slower TTFB but faster FCP than CSR?
- What is the hydration cost in SSR applications?
- When would you use CSR instead of SSR?
- How do you handle the development complexity of SSR?
Challenge: Build the same simple application (a todo list with 3 views) in two implementations: a CSR version with Vite + React, and an SSR version with Express + React + renderToString. Compare: bundle size, FCP, LCP, TTFB, TTI, server memory usage, development time, and lines of configuration. Document the differences.
FAQ
Mini Project
Build the same blog application (home page + 3 blog posts) in two versions: CSR with Vite + React (data fetched via useEffect), and SSR with Express + React renderToString (data fetched on server). For each version, measure and compare: FCP, LCP, TTFB, TTI, bundle size, Lighthouse performance score, and SEO score.
What's Next
You understand CSR vs SSR tradeoffs. Now learn React SSR Basics to start building server-rendered React applications.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro