How Client-Side Rendering (CSR) Works in Next.js

CSR (Client-Side Rendering) is an approach where page rendering happens entirely in the browser. The server sends minimal HTML with a JavaScript bundle, and React renders the content on the client side.

In a regular React app (without Next.js) all rendering is client-side. In Next.js CSR is also available but is used selectively, for parts of the interface that need interactivity or data only available in the browser.

When CSR Is Used in Next.js

In Next.js there are no pages that work entirely through CSR (like a regular React SPA). A server component always renders the initial HTML. But inside a page there can be client components that load data and render content on the client.

Typical scenarios:

  • Loading data based on user actions (filters, search)
  • Components using browser APIs (localStorage, geolocation)
  • Interactive widgets (chat, notifications, timers)

How to Implement It

Loading Data in a Client Component

'use client'

import { useState, useEffect } from 'react'

interface Problem {
  id: string
  name: string
  difficulty: number
}

export default function ProblemSearch() {
  const [query, setQuery] = useState('')
  const [results, setResults] = useState<Problem[]>([])
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    if (!query) {
      setResults([])
      return
    }

    setLoading(true)
    const controller = new AbortController()

    fetch(`/api/problems/search?q=${query}`, {
      signal: controller.signal
    })
      .then(res => res.json())
      .then(data => setResults(data))
      .finally(() => setLoading(false))

    return () => controller.abort()
  }, [query])

  return (
    <div>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="Search problems on Hack Frontend"
      />
      {loading && <p>Loading...</p>}
      <ul>
        {results.map(p => (
          <li key={p.id}>{p.name}</li>
        ))}
      </ul>
    </div>
  )
}

Dynamic Import with SSR Disabled

Sometimes a component cannot render on the server at all (uses window, document, etc.). In such cases you can disable SSR for a specific component:

import dynamic from 'next/dynamic'

const CodeEditor = dynamic(
  () => import('@/components/ide/code-editor'),
  { ssr: false, loading: () => <p>Loading editor...</p> }
)

export default function ProblemPage() {
  return (
    <div>
      <h1>JavaScript Problem</h1>
      <CodeEditor />
    </div>
  )
}

ssr: false guarantees the component will only be loaded and rendered in the browser.

Pattern: Server Wrapper + Client Interactivity

The right approach in Next.js: load initial data on the server and implement interactivity on the client:

// app/problems/page.tsx (server component)
import { db } from '@/lib/db'
import { ProblemFilter } from './problem-filter'

export default async function ProblemsPage() {
  const problems = await db.problem.findMany({
    select: { id: true, name: true, difficulty: true }
  })

  return (
    <div>
      <h1>Problems</h1>
      <ProblemFilter initialProblems={problems} />
    </div>
  )
}
// app/problems/problem-filter.tsx (client component)
'use client'

import { useState } from 'react'

interface Problem {
  id: string
  name: string
  difficulty: number
}

export function ProblemFilter({
  initialProblems
}: {
  initialProblems: Problem[]
}) {
  const [difficulty, setDifficulty] = useState<number | null>(null)

  const filtered = difficulty
    ? initialProblems.filter(p => p.difficulty === difficulty)
    : initialProblems

  return (
    <div>
      <div>
        <button onClick={() => setDifficulty(null)}>All</button>
        <button onClick={() => setDifficulty(1)}>Easy</button>
        <button onClick={() => setDifficulty(2)}>Medium</button>
        <button onClick={() => setDifficulty(3)}>Hard</button>
      </div>
      <ul>
        {filtered.map(p => (
          <li key={p.id}>{p.name}</li>
        ))}
      </ul>
    </div>
  )
}

Key principle:

In Next.js CSR is applied to individual components, not entire pages. Initial HTML always comes from the server, giving good SEO and fast first load. Interactivity is added on top.

Downsides of Pure CSR

  • SEO. Content rendered only on the client is invisible to search bots until JavaScript executes.
  • First load. The user sees an empty page or skeleton while JavaScript loads and runs.
  • Performance on weak devices. All rendering falls on the user's device CPU.

This is exactly why Next.js uses server components by default and CSR is applied only where it is needed.

Useful Resources