Server Rendering vs. Client Rendering
HTTP is stateless. Every request starts fresh — no memory of the last one. That creates a fundamental question for web applications: where do you render the UI?
Server rendering and client rendering take opposite sides of this trade-off. Understanding why requires seeing how each actually works.
What Client Rendering Means
In a client-rendered app, the server sends a near-empty HTML shell. JavaScript takes over in the browser and builds the entire UI.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My App</title>
</head>
<body>
<div id="app"></div>
<script src="/bundle.js"></script>
</body>
</html>
That’s it. The browser downloads the JavaScript bundle, runs it, and the UI appears. If JavaScript fails or is slow, the user sees nothing useful.
The dominant stack today is React, Vue, or Angular with a build tool like Vite or Webpack. Your code runs entirely in the browser:
// client.js — runs in the browser
import { createRoot } from 'react-dom';
import { App } from './App';
const root = createRoot(document.getElementById('app'));
root.render(<App />);
With client rendering, the server’s job is trivial: serve static files. You can host on any static file server, S3, or CDN. No application logic needs to run on the server at all.
What Server Rendering Means
In server rendering, the server executes your application code and sends back a fully-built HTML page. The browser receives something the user can already read.
// server.js — runs on the server
import { renderToString } from 'react-dom/server';
import { App } from './App';
const html = renderToString(<App />);
const response = `
<!DOCTYPE html>
<html lang="en">
<head>...</head>
<body>
<div id="app">${html}</div>
<script src="/bundle.js"></script>
</body>
</html>
`;
The page is visible immediately, even before the JavaScript bundle downloads and runs. Search engines see real content. Users on slow connections see something.
Modern SSR frameworks like Next.js (React) and Nuxt (Vue) blur the line — they run your components on the server for the initial render, then hand off to the client for subsequent navigation. This is called isomorphic or universal rendering.
The First Paint Problem
The most important difference between SSR and CSR shows up in how quickly the user sees real content.
With pure client rendering:
- Browser downloads HTML (fast, ~1KB)
- Browser downloads JavaScript bundle (slow, 100KB–1MB+)
- JavaScript executes (slow on mobile)
- UI appears
With server rendering:
- Browser downloads HTML (already contains rendered content)
- UI is visible immediately
- JavaScript downloads in background
- JavaScript “hydrates” — attaches event handlers without rebuilding the DOM
For a content-heavy page, SSR wins on first paint every time. For a highly interactive dashboard that requires all the JavaScript anyway, CSR’s head start shrinks.
// Hydration: React attaches event handlers to existing DOM
// No re-render, just wire up the handlers
import { hydrateRoot } from 'react-dom/client';
import { App } from './App';
hydrateRoot(document.getElementById('app'), <App />);
Data Fetching
How you fetch data changes completely between the two approaches.
Client rendering fetches data after the component mounts:
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(setUser);
}, [userId]);
if (!user) return <p>Loading...</p>;
return <h1>{user.name}</h1>;
}
This means two round-trips: one for the HTML, one for the data. On slow connections, you see a loading spinner twice.
Server rendering can fetch data before sending HTML:
// Next.js page component — runs on the server
export async function getServerSideProps(context) {
const { params } = context;
const res = await fetch(`https://api.example.com/users/${params.id}`);
const user = await res.json();
return {
props: { user } // passed to the component as props
};
}
function UserProfile({ user }) {
return <h1>{user.name}</h1>; // no loading state needed
}
The user sees the fully populated page in a single request. No loading spinner. No client-side waterfall.
For CMS-driven content — blogs, documentation, product pages — SSR’s data-fetching model is simpler and faster.
Search Engine Optimisation
Search engines need to see content. They run crawlers that fetch your URLs and index what they find.
With client rendering, crawlers historically saw an empty page. Modern crawlers (Googlebot since 2019) do run JavaScript, but the process is asynchronous — they crawl when they can, index when they’re ready. Results are slower to appear and less reliable.
With server rendering, the content is in the initial HTML. Crawlers see it immediately, reliably, on first crawl. This matters for any public-facing content you want indexed: marketing pages, blog posts, documentation.
Time to Interactive
First paint isn’t the whole story. SSR pages often have a long Time to Interactive (TTI) because the JavaScript still needs to hydrate. If the JS bundle is large, users can see a rendered page they can’t interact with — links don’t work, buttons don’t respond.
Client rendering, once loaded, is immediately interactive. The entire UI arrives in one shot.
This creates a useful mental model:
| Metric | SSR wins | CSR wins |
|---|---|---|
| First Contentful Paint | ✓ | |
| Time to Interactive | ✓ (for small apps) | |
| SEO (reliability) | ✓ | |
| Server infrastructure cost | ✓ | |
| Interaction-heavy apps | ✓ |
For a content site like a blog or documentation, SSR wins overall. For a complex dashboard, CSR may be simpler despite the longer load time.
Partial Hydration and Islands
The newest approach tries to get the best of both. Partial hydration (or “islands architecture”) renders everything on the server, but only makes interactive components “alive” on the client:
// Static content — no JS sent to client
<ArticleBody content={post.body} />
// Interactive component — JavaScript shipped only for this
<CommentSection postId={post.id} />
Astro pioneered this with its component islands. Next.js adopted it with React Server Components. The idea is the same: server-render what you can, hydrate only what must be interactive.
Choosing Between Them
No single answer fits everything. Here’s how to think about it:
Use server rendering when:
- Content is public and should be indexed
- First-paint speed matters (slow connections, mobile)
- Data doesn’t change second-by-second
- The UI is mostly static after load
Use client rendering when:
- The UI is highly interactive (dashboards, editors, games)
- Real-time data updates are frequent
- Most content is personalised per user
- The app works without a server (offline-first)
Most real applications use both. A SaaS dashboard might use CSR for the main app and SSR for landing pages and documentation. Next.js and Nuxt make this straightforward — you opt in to SSR per route.