NextjsClient-Side Rendering (CSR)

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

TSX
'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

The App Router flips the default
In older React SPAs, CSR was the norm — there was no other option without extra tooling. In the App Router, the default is the opposite: components are Server Components unless you explicitly opt into "use client". CSR is now the exception you reach for deliberately, not the water you swim in.
Tip
If a page's content is meaningful for SEO or for the initial paint — product details, articles, marketing pages — render it on the server. Save CSR for the interactive slice of the page that truly needs browser-only state or a tight feedback loop.
Warning
Overusing CSR in the App Router reintroduces the exact problems it was designed to avoid: larger client JavaScript bundles, slower first content, layout shift while data loads, and worse SEO. Reach for a Server Component first, and drop down to a Client Component only for the parts that actually need interactivity or browser APIs.