'어떤 렌더링?'이 아니라 '왜 이 렌더링?'을 먼저 물어야 합니다
솔직히 말하면, CSR·SSR·SSG·ISR 비교 글은 이미 인터넷에 넘쳐납니다. 그런데 실무에서 기획 티켓을 받아들고 막상 전략을 결정하려고 하면 머릿속이 백지가 되는 경험, 다들 한 번씩 있으실 거예요. 개념은 아는데 결정 기준이 없는 겁니다. 이 글은 그 '결정 기준'을 다루려 합니다—각 전략의 동작 원리를 살짝 건드리되, 핵심은 어떤 상황에서 무엇을 골라야 하는가입니다.
CSR: SPA의 쾌적함, 초기 로딩의 대가
CSR(Client-Side Rendering)은 브라우저가 빈 HTML을 받아 JavaScript 번들을 실행하면서 화면을 그립니다. 사용자가 처음 접속하면 bundle.js를 다운로드하고 React 앱이 마운트될 때까지 사실상 빈 화면입니다. velog의 렌더링 전략 정리 글이 노션을 예시로 든 것처럼—노션을 처음 켤 때 '무겁다'는 느낌이 드는 게 바로 이 초기 로딩 비용입니다.
Lighthouse를 돌려보면 CSR 앱은 LCP(Largest Contentful Paint)가 특히 취약합니다. JS 파싱·실행이 끝나야 의미 있는 콘텐츠가 그려지니까요. 반면 초기 로딩 이후 페이지 이동은 전체 HTML을 다시 받지 않고 API 응답만으로 화면을 교체하기 때문에 인터랙션 속도는 매우 빠릅니다.
선택 기준: 로그인이 필요한 대시보드, 어드민 툴, 사내 도구처럼 SEO가 전혀 필요 없고 재방문 사용자가 대부분인 페이지. 번들 사이즈 관리만 잘 한다면 가장 개발 경험이 좋은 선택입니다.
SSR: SEO와 초기 화면 속도, 서버 비용의 트레이드오프
SSR(Server-Side Rendering)은 요청이 들어올 때마다 서버가 데이터를 가져와 완성된 HTML을 만들어 응답합니다. 브라우저는 JS가 실행되기 전에도 콘텐츠를 볼 수 있어 FCP(First Contentful Paint)가 빠르고, 검색 엔진 크롤러도 완성된 HTML을 읽기 때문에 SEO에 유리합니다.
문제는 매 요청마다 서버 연산이 발생한다는 겁니다. 트래픽이 몰리면 서버 비용이 선형으로 올라가고, TTFB(Time to First Byte)가 늘어나면 오히려 사용자 경험이 나빠질 수 있습니다. 'Figma에서 볼 때는 빠른데 실제 배포 환경에서 왜 이렇게 느리지?' 하는 상황이 여기서 자주 납니다—로컬에서는 DB가 가깝고, 프로덕션에서는 cold start와 쿼리 레이턴시가 고스란히 TTFB에 더해지거든요.
선택 기준: 실시간성이 중요한 뉴스, 상품 재고처럼 매 요청마다 최신 데이터가 보장되어야 하는 페이지. 서버 인프라 비용을 감당할 수 있는 규모인지 먼저 확인해야 합니다.
SSG: 가장 빠른 응답, 가장 굳어있는 데이터
SSG(Static Site Generation)는 빌드 타임에 HTML을 미리 만들어둡니다. 요청이 오면 서버 연산 없이 CDN에서 바로 파일을 내려주기 때문에 TTFB가 거의 0에 수렴합니다. Core Web Vitals 점수를 좋게 받고 싶다면 SSG가 가장 유리한 출발점입니다.
단점은 명확합니다. 데이터가 바뀌면 다시 빌드해야 합니다. 포스트가 1만 개인 블로그를 SSG로 만들면 빌드 시간이 수십 분이 될 수 있고, 그동안 발행된 새 글은 배포 전까지 반영되지 않습니다. 'CDN에 올려두면 빠르다'는 장점이 '데이터가 굳어있다'는 단점과 정확히 맞교환입니다.
선택 기준: 마케팅 랜딩 페이지, 약관·정책 페이지, 변경 빈도가 매우 낮은 문서 사이트. 데이터 갱신 주기가 배포 주기보다 길다면 SSG가 정답입니다.
ISR: SSG의 속도 + SSR의 신선함, 단 '딱 한 번'의 낡은 응답을 감수해야
ISR(Incremental Static Regeneration)은 SSG의 치명적 단점을 보완하기 위해 등장했습니다. Next.js의 revalidate 옵션이 대표적인 구현인데, 동작 방식이 꽤 독특합니다.
revalidate: 60으로 설정하면:
1. 최초 요청 → 미리 생성된 정적 HTML 응답
2. 60초 경과 후 첫 요청 → 여전히 기존 HTML 응답 (사용자는 낡은 데이터를 봄)
3. 동시에 서버가 백그라운드에서 새 HTML 재생성
4. 그 다음 요청부터 → 새로 만든 HTML 응답
여기서 기획자와 마찰이 생깁니다. '실시간으로 반영된다고 했는데 왜 이게 안 바뀌어요?'—revalidate 주기 동안은 의도적으로 낡은 데이터를 제공하는 설계라는 걸 기획 단계에서 공유하지 않으면 QA 때 패닉이 옵니다. ISR은 stale-while-revalidate 패턴의 HTTP 캐시 전략과 동일한 철학입니다. 이 트레이드오프를 팀 전체가 이해하고 있어야 합니다.
선택 기준: 쇼핑몰 상품 페이지, 미디어 아카이브처럼 자주 바뀌지는 않지만 주기적으로 업데이트가 필요한 콘텐츠. revalidate 주기는 데이터의 '허용 가능한 낡음(stale tolerance)'을 기준으로 설정해야 합니다.
결정 기준: 이 두 가지 질문으로 전략이 좁혀집니다
렌더링 전략을 고를 때 제가 실제로 쓰는 판단 프레임은 단 두 가지입니다.
① 이 페이지에 SEO가 필요한가? - NO → CSR로 충분. 인터랙션 중심의 앱이라면 CSR + 코드 스플리팅 + 로딩 스켈레톤 조합이 사용자 경험 면에서 가장 쾌적합니다. - YES → 다음 질문으로.
② 데이터가 얼마나 자주 바뀌는가? - 거의 안 바뀜 (배포 주기보다 긴 갱신 주기) → SSG - 가끔 바뀜 (허용 가능한 stale 구간이 있음) → ISR - 항상 최신이어야 함 (재고, 실시간 가격 등) → SSR
현실에서는 단일 전략보다 하이브리드가 더 많습니다. Next.js App Router가 페이지 단위가 아니라 컴포넌트 단위로 렌더링 전략을 섞을 수 있게 설계된 것도 이 이유입니다. 상품 상세 레이아웃은 ISR로, 재고 수량 표시는 CSR로, 상품명·설명은 SSG로—이렇게 컴포넌트 단위로 쪼개서 생각하는 게 2024년 이후의 실무 감각에 맞습니다.
상태 관리와의 연결: 렌더링 전략이 바뀌면 상태 설계도 바뀝니다
렌더링 전략 이야기를 하면서 상태 관리를 빠뜨리면 반쪽짜리 논의가 됩니다. CSR에서는 클라이언트 상태가 전부지만, SSR·ISR에서는 서버에서 내려온 초기 데이터와 클라이언트 상태의 hydration 시점이 미묘하게 어긋날 수 있습니다.
velog의 React useState 관련 글이 잘 정리한 것처럼, React의 상태 설계 원칙은 간단합니다—움직이는 조각은 적을수록 좋다. isTyping과 isSubmitting이 동시에 true가 될 수 없는 것처럼, SSR에서 서버 상태와 클라이언트 상태가 충돌하는 hydration mismatch도 결국 같은 종류의 문제입니다. 상태가 어디서 시작되는지(서버 vs 클라이언트), 무엇이 source of truth인지를 명확히 해야 합니다.
dev.to에서 소개된 Angular Signal Forms가 흥미로운 이유도 여기 있습니다. 기존 Reactive Forms가 TypeScript 모델, FormGroup 트리, 템플릿 바인딩을 세 곳에서 동기화해야 했던 구조—이게 정확히 SSR에서 서버 데이터와 클라이언트 상태를 두 곳에서 관리하는 고통과 같은 패턴입니다. Signal Forms가 '모델이 source of truth'라는 철학으로 이 문제를 해결하려는 것처럼, 렌더링 전략도 '데이터가 어디서 태어나는가'를 먼저 정의하면 결정이 훨씬 쉬워집니다.
실무 시사점: 기획 단계에서 이 대화를 먼저 해야 합니다
렌더링 전략은 개발자가 혼자 결정하는 기술 선택지가 아닙니다. ISR의 stale 구간, SSG의 빌드 배포 의존성—이것들은 기획·디자인·운영팀 모두가 이해해야 하는 서비스 동작의 제약 조건입니다.
'왜 방금 등록한 상품이 안 보여요?'라는 CS가 올라오기 전에, '이 페이지는 최대 60초까지 이전 데이터가 노출될 수 있다'는 합의가 먼저 있어야 합니다. 그 합의를 이끌어내는 것도 프론트엔드 개발자의 역할입니다.
전망: 렌더링 전략의 경계가 흐려지는 방향
Next.js App Router의 React Server Components, Remix의 loader 모델, Astro의 Islands 아키텍처—모두 '페이지 단위 렌더링 전략'에서 컴포넌트 단위 렌더링 전략으로 이동하는 흐름입니다. CSR/SSR/SSG/ISR이라는 네 칸짜리 분류는 점점 더 '이 컴포넌트가 어떤 데이터 생명주기를 갖는가'라는 질문으로 대체될 겁니다.
Container Queries나 CSS :has()처럼 레이아웃 맥락을 컴포넌트 안으로 가져오는 흐름과 정확히 평행합니다. 렌더링 컨텍스트도, 스타일 컨텍스트도—결국 '더 작은 단위로, 더 명시적으로'가 지금 프론트엔드 전반의 방향입니다. 그 흐름을 이해하고 있으면, 다음에 새로운 렌더링 패턴이 나와도 당황하지 않습니다.