Ch 03. 브라우저가 직접 그리다 — CSR
재료만 보내고 조리는 손님이 직접
앞 챕터의 식당 비유를 이어가겠습니다. 이번에는 다른 식당입니다. 손님이 자리에 앉으면 생재료와 조리 도구가 배달됩니다. 손님이 직접 요리해서 먹습니다. 처음엔 기다려야 하지만, 한 번 차려지면 손님이 원하는 대로 바꿀 수 있습니다. 고기를 더 굽거나, 소스를 다르게 넣거나.
CSR(Client-Side Rendering)이 이런 방식입니다. 서버는 거의 빈 HTML과 자바스크립트 파일을 보냅니다. 브라우저는 자바스크립트를 내려받아 실행하면서 DOM을 직접 만들고 화면을 그립니다.
리액트의 기본 동작 방식
Create React App으로 만든 순수 리액트 앱은 기본적으로 CSR입니다. index.html을 열면 <div id="root"></div> 하나만 있고, 자바스크립트가 로드된 후에야 그 안에 내용이 채워집니다.
<!-- 서버에서 받은 HTML (CSR 방식) --><!DOCTYPE html><html> <head> <title>My App</title> </head> <body> <div id="root"></div> <script src="/bundle.js"></script> </body></html>```text자바스크립트가 실행되기 전까지 사용자는 빈 화면을 봅니다. 번들 파일이 크다면 흰 화면이 꽤 오래 지속될 수 있습니다.### 언제 CSR을 쓰는가Next.js에서 CSR이 필요한 상황은 명확합니다.**사용자 상호작용이 중심인 UI.** 클릭하면 바뀌고, 입력하면 반응하고, 드래그하면 움직이는 것들. 이런 동적인 UI는 브라우저 환경에서만 동작합니다. 서버에는 마우스도 없고 키보드도 없습니다.**브라우저 전용 API가 필요한 경우.** `localStorage`, `sessionStorage`, `window`, `navigator` 같은 것들은 서버에 존재하지 않습니다. 이런 API를 사용하는 코드는 클라이언트에서만 실행되어야 합니다.**실시간으로 바뀌는 UI.** 초마다 업데이트되는 타이머, 실시간 채팅, 드래그 앤 드롭 등은 클라이언트에서 처리하는 게 자연스럽습니다.### `"use client"` 컴포넌트에서의 데이터 페칭Next.js에서 CSR을 사용하려면 파일 최상단에 `"use client"`를 선언합니다. 이 컴포넌트는 클라이언트에서 실행되며, `useState`, `useEffect` 같은 리액트 훅을 사용할 수 있습니다.데이터를 클라이언트에서 가져오는 전형적인 패턴입니다.```tsx// 파일: app/dashboard/components/RealtimeCounter.tsx'use client';import { useState, useEffect } from 'react';interface Stats { visitors: number; pageViews: number; updatedAt: string;}export default function RealtimeCounter() { const [stats, setStats] = useState<Stats | null>(null); const [loading, setLoading] = useState(true); useEffect(() => { async function fetchStats() { try { const res = await fetch('/api/stats'); const data: Stats = await res.json(); setStats(data); } finally { setLoading(false); } } fetchStats(); // 30초마다 갱신 const interval = setInterval(fetchStats, 30_000); return () => clearInterval(interval); }, []); if (loading) return <p>불러오는 중...</p>; if (!stats) return <p>데이터를 가져오지 못했습니다.</p>; return ( <div> <p>현재 방문자: {stats.visitors.toLocaleString()}명</p> <p>페이지 뷰: {stats.pageViews.toLocaleString()}</p> <p>마지막 갱신: {stats.updatedAt}</p> </div> );}
useEffect 안에서 fetch를 호출합니다. 컴포넌트가 처음 마운트될 때 데이터를 가져오고, setInterval로 30초마다 새로 가져옵니다. 클린업 함수에서 clearInterval을 호출해 컴포넌트가 언마운트될 때 인터벌을 정리합니다.
CSR의 장단점
장점은 풍부한 상호작용입니다. 서버에 매번 요청하지 않고 브라우저 안에서 상태를 관리하므로, 빠르고 반응성 있는 UI를 만들 수 있습니다.
단점은 두 가지입니다. 첫째, 초기 로딩이 느립니다. 자바스크립트 번들을 내려받고 실행해야 하므로, 첫 화면이 나타나기까지 시간이 걸립니다. 둘째, SEO가 어렵습니다. 검색 크롤러 입장에서는 빈 HTML만 보이기 때문입니다. 물론 Next.js는 이 문제를 Server Component와의 조합으로 상당 부분 해결합니다.
다음 챕터에서는
다음 챕터에서는 요청이 오기 전에 미리 HTML을 만들어두는 방법을 알아봅니다. 빌드 시점에 모든 페이지를 완성해두는 SSG입니다. 서버 부하도 없고, 속도도 빠르고, SEO도 좋습니다. 단, 조건이 있습니다.