Client-Side Rendering (CSR)
Client-Side Rendering means the browser does almost all of the work. The server sends down a nearly empty HTML shell plus a JavaScript bundle, the browser downloads and runs that JavaScript, and only then does the actual content — the markup a visitor can see and read — get built and inserted into the page. Between the first response arriving and the JavaScript finishing its job, the user is often looking at a blank page, a spinner, or a skeleton screen.
This was the default (and often the only option) for classic single-page React apps created with tools like Create React App. The server — or often just a static file host — served one HTML file with a single
, and every single thing you saw was rendered by React running entirely in the browser.What CSR looks like in practice
A CSR page typically fetches its own data after it mounts, using something like useEffect combined with useState, and shows a loading state until that fetch resolves.
app/dashboard/Chart.tsx — a Client Component doing CSR
'use client'
import { useEffect, useState } from 'react'
type Stats = { visitors: number; signups: number }
export default function Chart() {
const [stats, setStats] = useState<Stats | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
let cancelled = false
fetch('/api/dashboard-stats')
.then((res) => res.json())
.then((data: Stats) => {
if (!cancelled) setStats(data)
})
.finally(() => {
if (!cancelled) setLoading(false)
})
return () => {
cancelled = true
}
}, [])
if (loading) return <p>Loading dashboard…</p>
if (!stats) return <p>Could not load stats.</p>
return (
<div>
<p>Visitors: {stats.visitors}</p>
<p>Signups: {stats.signups}</p>
</div>
)
}In practice, most CSR code today does not hand-roll useEffect/useState fetching like the example above — it reaches for a dedicated client-side data library such as SWR or React Query (TanStack Query), which adds caching, deduplication, and revalidation on top of the same basic idea. Either way, the defining trait of CSR is the same: the fetch and the render both happen in the browser, after the page has already loaded.
Where CSR still fits in the App Router
The App Router pushes rendering toward the server by default, but CSR has not disappeared — it is simply scoped down to the pieces of a page that genuinely need it. You reach for it inside a Client Component (marked with the "use client" directive) when you need:
Highly interactive UI that reacts instantly to user input without a round trip — drag-and-drop boards, rich text editors, live filtering.
Data that is tied to the browser itself — window size, localStorage, geolocation, WebSocket connections.
Dashboards or admin panels behind authentication where search engines will never see the content, so SEO is a non-issue.
Data that changes so often that fetching it on every server render would be wasteful compared to polling or subscribing from the client.
Aspect | Client-Side Rendering |
|---|---|
Where it runs | Entirely in the browser, after JS loads |
First response | Near-empty HTML shell |
Perceived loading | Spinner/skeleton until fetch + render finish |
SEO | Poor — crawlers may not execute JS or wait for data |
Best for | Interactive, authenticated, highly dynamic UI |