Skip to content

CSR vs SSR — Client-Side Rendering Versus Server-Side Rendering Compared

DodaTech Updated 2026-06-28 6 min read

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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

  1. How does CSR affect First Contentful Paint compared to SSR?
  2. Why does SSR have slower TTFB but faster FCP than CSR?
  3. What is the hydration cost in SSR applications?
  4. When would you use CSR instead of SSR?
  5. 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

Does SSR replace the need for a CDN?

No. SSR still benefits from a CDN for static assets, caching, and edge delivery. Use a CDN in front of your SSR server for best performance.

Can I combine CSR and SSR in one app?

Yes. This is called a hybrid approach. Use SSR for initial page load and SEO-critical content. Use CSR for subsequent navigations and interactive sections.

Is SSR harder to debug?

Yes, because you need to debug both server and client environments. Server errors, hydration mismatches, and environment-specific code add complexity.

Does SSR work with authentication?

Yes. SSR can handle authenticated requests. The server checks the session or token, fetches user-specific data, and renders the page with that data.

What is the most popular SSR framework?

Next.js (React) is the most popular SSR framework, followed by Nuxt.js (Vue), Remix (React), and SvelteKit (Svelte).

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