본문 바로가기
코드의 해부학

SSR, SSG, ISR : 당신의 '로딩 미각'을 만족시킬 전략은?

by Zev 2025. 6. 2.
728x90
반응형

🧠 SSR, SSG, ISR: 웹 렌더링 전략

현대적인 프론트엔드 아키텍처에서 서버와 클라이언트의 경계는 점차 모호해지고 있습니다. 그러나 웹 페이지 렌더링 시점을 명확히 이해하고 적절한 전략을 선택하지 않으면, 시스템의 성능 병목(performance bottlenecks), 자원 비효율성(resource inefficiencies), 그리고 **유지 보수 비용(maintenance overhead)**이 기하급수적으로 증가할 수 있습니다. 이는 단순히 "빠른 로딩"을 넘어선, 시스템의 근본적인 확장성과 안정성에 직결되는 문제입니다.

이 글에서는 SSR, SSG, ISR을 단순 비교를 넘어, 각 렌더링 전략의 내부 메커니즘(internal mechanisms), 기술적 구성 요소(technical components), 그리고 시스템 아키텍처에 미치는 **영향(implications)**을 컴퓨터 과학 전공자 수준에서 심층적으로 분석하고자 합니다.


0. 용어 정의 및 전제: 렌더링의 본질 이해

웹 렌더링 전략을 논하기 전에, 핵심 용어에 대한 명확한 이해와 전제 조건을 확립하는 것이 중요합니다.

🔹 무엇을 렌더링한다는 뜻인가?

웹 환경에서 '렌더링'이라 함은 단순히 브라우저 화면에 무언가를 그리는 행위를 넘어섭니다. 여기서는 특히 **React/Vue와 같은 Virtual DOM 기반의 컴포넌트 코드(JavaScript)**를 기반으로, 사용자에게 최종적으로 제공될 HTML 문자열을 생성하는 과정을 의미합니다. 이는 JavaScript 런타임 환경에서 JSX/Template 코드가 실제 DOM 엘리먼트를 표현하는 HTML 구조로 변환되는 프로세스를 내포합니다.

🔹 “렌더링 시점”이란?

렌더링이 발생하는 주체와 시점에 따라 웹 페이지 제공 방식이 결정됩니다.

  • Build Time: 개발자가 소스 코드를 배포하기 위해 빌드(build) 프로세스를 수행하는 시점입니다. 이 시점에 렌더링된 콘텐츠는 정적인 형태로 고정되어 배포됩니다.
  • Request Time (Runtime): 사용자가 웹 브라우저를 통해 특정 페이지를 요청(request)하는 시점입니다. 이 시점에 서버에서 동적으로 콘텐츠를 생성하여 응답합니다.
  • Revalidation Time: SSG 방식으로 빌드된 페이지가 일정 시간 또는 특정 이벤트 발생 후 서버에 의해 갱신되는 시점입니다 (ISR). 이는 빌드 타임과 런타임의 중간 지점에 해당하며, 캐시된 콘텐츠의 유효성을 재검증하고 필요한 경우 백그라운드에서 새로운 콘텐츠를 생성합니다.

1️⃣ SSR: 요청 시점의 렌더링 (Request-Time Rendering)

**서버 사이드 렌더링(Server-Side Rendering)**은 사용자의 모든 페이지 요청에 대해 서버가 동적으로 HTML을 생성하여 응답하는 전통적인 방식입니다. 이는 웹 서버가 요청-응답 주기에 따라 렌더링 작업을 수행하는 모델입니다.

🔬 내부 구조 분석 (Deep Dive)

  1. 클라이언트 요청: 사용자가 브라우저에서 특정 URL (예: GET /page)로 HTTP 요청을 보냅니다.
  2. 서버 수신 및 초기화: 웹 서버 (일반적으로 Node.js 환경의 Next.js 또는 Nuxt.js 서버)는 해당 요청을 수신하고, 요청 경로에 매핑된 컴포넌트와 getServerSideProps() (Next.js 기준)와 같은 데이터 Fetching 함수를 찾습니다.
  3. 데이터 Fetching: 서버는 getServerSideProps() 함수 내에서 정의된 로직에 따라 필요한 데이터를 외부 API, 데이터베이스 또는 기타 백엔드 서비스로부터 동기적 또는 비동기적으로(await) Fetch합니다. 이 과정에서 네트워크 I/O 및 데이터 처리 지연이 발생할 수 있습니다.
  4. 서버 렌더링: Fetch된 데이터를 기반으로, 서버는 React 컴포넌트를 ReactDOMServer.renderToString() 또는 renderToPipeableStream()과 같은 함수를 사용하여 **HTML 문자열로 변환(serialize)**합니다. 이 과정은 서버의 CPU 자원을 상당히 소모합니다.
  5. HTML 및 JS 번들 응답: 서버는 생성된 HTML 문자열과 함께, 클라이언트 측에서 Hydration 및 상호작용을 위해 필요한 **JavaScript 번들(bundle)**을 HTTP 응답으로 클라이언트에 전송합니다.
  6. 클라이언트 파싱 및 Hydration: 클라이언트 브라우저는 수신된 HTML을 즉시 파싱하여 페이지를 표시합니다. 이로 인해 **첫 번째 콘텐츠가 그려지는 시간(FCP: First Contentful Paint)**이 빨라질 수 있습니다. 이후 JavaScript 번들이 로드되고 실행되면, React 또는 Vue는 이미 렌더링된 HTML에 **이벤트 핸들러를 연결하고 DOM 트리를 재사용(Hydration)**하여 상호작용 가능한 애플리케이션으로 전환합니다. 이 과정에서 **상호작용까지의 시간(TTI: Time To Interactive)**이 결정됩니다.

📂 기술적 구성 요소

  • Node.js Runtime Environment: 서버 측 렌더링을 위한 JavaScript 실행 환경을 제공합니다.
  • **ReactDOMServer.renderToString()/renderToPipeableStream()**: React 컴포넌트를 서버에서 HTML 문자열로 렌더링하는 핵심 API입니다. renderToPipeableStream은 스트리밍을 지원하여 더 빠른 초기 응답을 가능하게 합니다.
  • Universal JavaScript: 서버와 클라이언트 모두에서 실행될 수 있는 JavaScript 코드를 작성해야 합니다. 이는 개발 복잡성을 증가시킬 수 있습니다.
  • HTTP Request-Response Cycle: 각 요청마다 서버에서 완전한 렌더링 파이프라인을 수행하므로, 서버의 부하가 직접적으로 요청 수에 비례합니다.

🔧 장점 (System Perspective)

  • 항상 최신 데이터 반영: 매 요청마다 데이터를 Fetch하고 렌더링하므로, 사용자에게 항상 최신 상태의 데이터를 제공할 수 있습니다. 이는 주식 시세, 실시간 알림, 개인화된 대시보드 등 **데이터 신선도(data freshness)**가 critical한 애플리케이션에 적합합니다.
  • 사용자 세션 기반 콘텐츠 적합: 사용자 인증 정보나 세션 데이터를 기반으로 동적으로 콘텐츠를 생성할 수 있으므로, 로그인된 사용자에게 맞춤형 정보를 제공하는 대시보드나 개인 프로필 페이지에 용이합니다.
  • 초기 SEO 용이성: 검색 엔진 크롤러가 JavaScript 실행 없이도 완전한 HTML을 즉시 파싱할 수 있어 SEO(Search Engine Optimization)에 매우 유리합니다. (물론 모던 크롤러는 JS 실행도 가능하지만, 초기 로딩 속도 측면에서 이점이 있습니다.)

⚠️ 단점 (Performance & Scalability Challenges)

  • 서버 부하 증가: 각 요청마다 CPU 집약적인 렌더링 작업을 수행해야 하므로, 서버의 CPU 사용량이 요청 수에 비례하여 급증합니다. 대규모 트래픽 발생 시 서버 증설 및 부하 분산 전략이 필수적입니다.
  • CDN 캐싱의 어려움: 동적으로 생성되는 HTML 특성상 CDN(Content Delivery Network)을 통한 캐싱이 매우 어렵거나 불가능합니다. 이는 모든 요청이 원본 서버까지 도달해야 함을 의미하며, 전역적인 사용자에게 균일한 성능을 제공하기 어렵게 만듭니다.
  • TTFB(Time To First Byte) 지연: 서버에서 데이터 Fetch, 렌더링 과정을 모두 마친 후 HTML을 전송하므로, 첫 번째 바이트를 수신하는 시간(TTFB)이 길어질 수 있습니다. 이는 사용자 경험에 부정적인 영향을 미칠 수 있습니다.
  • 높은 운영 비용: 서버 자원 소모가 크고, 확장성을 위해 복잡한 서버 인프라 (오토 스케일링, 로드 밸런싱 등) 구축이 필요하여 운영 비용이 증가합니다.

2️⃣ SSG: 정적 HTML로 미리 생성 (Build-Time Rendering)

**정적 사이트 생성(Static Site Generation)**은 빌드 시점에 모든 페이지의 HTML을 미리 생성해두고, 사용자의 요청 시에는 단순히 이 정적 파일을 제공하는 방식입니다. 이는 웹 서버가 요청 시점에 아무런 렌더링 작업을 수행하지 않는 모델입니다.

🔬 내부 구조 분석 (Deep Dive)

  1. 빌드 시점(next build): 개발자가 애플리케이션을 배포하기 위해 next build와 같은 명령어를 실행합니다.
  2. getStaticProps() 호출: Next.js와 같은 프레임워크는 빌드 프로세스 중에 각 페이지 컴포넌트의 getStaticProps() (Next.js 기준) 함수를 호출합니다. 이 함수는 오직 빌드 시점에만 실행됩니다.
  3. 데이터 Fetching: getStaticProps() 내에서 정의된 로직에 따라 필요한 데이터를 외부 API, 파일 시스템, 또는 CMS(Content Management System)로부터 Fetch합니다. 이 데이터는 빌드 시점에 고정됩니다.
  4. 페이지 단위 HTML 생성: Fetch된 데이터를 기반으로, 각 페이지에 대한 React 컴포넌트가 서버(빌드 환경)에서 ReactDOMServer.renderToString()을 사용하여 완전한 HTML 문자열로 렌더링됩니다.
  5. 정적 파일 저장: 생성된 HTML 파일과 해당 페이지에 필요한 CSS, JavaScript 번들 등의 정적 자산은 빌드 아웃풋 디렉토리 (예: /.next/static 또는 /out)에 저장됩니다.
  6. CDN 배포: 이 정적 파일들은 Amazon S3, Cloudflare Pages, Netlify 등과 같은 정적 호스팅 서비스나 CDN 엣지 노드에 배포됩니다.
  7. 클라이언트 요청 및 제공: 사용자가 브라우저에서 페이지를 요청하면, CDN 엣지 노드가 가장 가까운 위치에서 캐시된 정적 HTML 파일과 JavaScript 번들을 즉시 제공합니다. 서버는 렌더링에 관여하지 않습니다.
  8. 런타임 JS 로딩 후 Hydration: 클라이언트는 수신된 HTML을 즉시 파싱하여 표시하고, 이어서 JavaScript 번들을 로드하여 Hydration 과정을 통해 상호작용 가능한 상태로 만듭니다.

📂 기술적 구성 요소

  • Static Site Generator (SSG) Framework: Next.js, Gatsby, Hugo, Jekyll 등 빌드 시점에 HTML을 생성하는 도구입니다.
  • **getStaticProps()/getStaticPaths():** Next.js의 핵심 API로, 빌드 시점에 데이터를 Fetch하고 동적 경로의 페이지들을 생성할 수 있도록 합니다.
  • Immutable Artifacts: HTML, CSS, JS 파일들은 빌드 후 변경되지 않는 불변(immutable) 자산으로 취급됩니다.
  • CDN (Content Delivery Network): 전 세계에 분산된 엣지 서버를 통해 정적 파일을 사용자에게 가장 빠르게 전송하는 핵심 인프라입니다.

🧠 메리트 (Optimized for Performance & Scalability)

  • 요청 처리 부하 없음: 런타임에 서버가 HTML을 렌더링할 필요가 없어 서버 부하가 사실상 '0'에 수렴합니다. 이는 대규모 동시 접속에도 매우 안정적입니다.
  • 전 세계 CDN을 통한 극대화된 성능: 모든 콘텐츠가 정적 파일이므로, CDN 엣지 노드에서 직접 서빙할 수 있습니다. 이는 사용자에게 가장 가까운 위치에서 콘텐츠를 제공하여 극도로 빠른 TTFB와 FCP를 보장합니다. 글로벌 서비스에 매우 유리합니다.
  • 최적화된 SEO: 빌드 시점에 완전한 HTML이 생성되므로, 검색 엔진 크롤러가 매우 효율적으로 페이지를 인덱싱할 수 있습니다.
  • 보안 강화: 서버 측 로직이 최소화되거나 없으므로, 공격 표면(attack surface)이 줄어들어 보안에 강점을 가집니다.
  • 낮은 운영 비용: 별도의 런타임 서버 인프라가 필요 없어 호스팅 비용이 저렴합니다.

🧱 제약 (Limitations)

  • 동적 데이터 반영 어려움: 데이터가 변경될 때마다 전체 또는 해당 페이지를 **재빌드(rebuild)하고 재배포(redeploy)**해야 합니다. 이는 데이터 신선도가 중요한 애플리케이션에는 적합하지 않습니다.
  • 실시간성 부적합: 주식 시세, 실시간 채팅, 날씨 정보 등 실시간 업데이트가 필요한 콘텐츠에는 사용할 수 없습니다.
  • 빌드 시간 문제: 웹사이트 규모가 커지거나 페이지 수가 많아질수록 빌드 시간이 길어져 개발 및 배포 워크플로우에 병목이 될 수 있습니다.

3️⃣ ISR: 정적 + 동적의 균형 (Revalidate-Based Rendering)

**증분 정적 재생성(Incremental Static Regeneration)**은 SSG의 장점인 빠른 초기 응답과 CDN 활용성을 유지하면서도, 데이터 신선도 문제를 보완하기 위해 일정 주기로 또는 필요에 따라 페이지를 백그라운드에서 갱신하는 Next.js의 고유한 렌더링 전략입니다.

🔬 구조 분석 (Architectural Hybrid)

ISR은 빌드 타임 렌더링과 런타임 렌더링의 하이브리드 접근 방식입니다.

  1. 빌드 시점 SSG: next build 시점에 getStaticProps()와 함께 revalidate 옵션을 설정합니다 (예: revalidate: 60은 60초마다 페이지를 재생성할 수 있음을 의미). SSG와 동일하게 초기 HTML을 생성하여 정적 파일로 저장하고 CDN에 배포합니다.
  2. 클라이언트 첫 요청: 사용자가 페이지를 처음 요청하면, CDN은 빌드 시점에 생성된 정적 HTML 파일을 즉시 제공합니다. 이 시점까지는 SSG와 동일한 성능 이점을 가집니다.
  3. 캐시 유효 기간 만료(TTL Expiration): revalidate 값으로 설정된 시간이 경과하면, 해당 페이지의 캐시된 버전은 '만료됨(stale)' 상태가 됩니다.
  4. 다음 요청 시 재생성 트리거: 만료된 페이지에 대해 새로운 사용자 요청이 들어오면:
    • 즉시 오래된(stale) HTML을 반환합니다. (사용자는 여전히 빠른 응답을 받습니다.)
    • 백그라운드에서 서버는 해당 페이지의 getStaticProps()를 다시 실행하여 최신 데이터를 Fetch하고, 새로운 HTML을 렌더링합니다. 이 과정은 사용자의 요청을 블록하지 않습니다.
  5. 새 HTML 생성 완료 및 캐시 교체: 새로운 HTML이 성공적으로 생성되면, 서버는 CDN의 캐시를 업데이트하거나 로컬 캐시를 갱신합니다. 이후의 모든 요청은 새롭게 생성된 HTML을 받게 됩니다.
  6. on-demand revalidation (선택적): 특정 이벤트 (예: CMS에서 콘텐츠 업데이트)가 발생했을 때 API 호출을 통해 수동으로 특정 페이지의 재생성을 트리거할 수도 있습니다. 이는 revalidate 시간 주기를 기다리지 않고 즉각적인 갱신이 필요할 때 유용합니다.

📂 내부 메커니즘 (Under the Hood)

  • 서버 측 캐시 관리: Next.js 서버는 내부적으로 각 페이지의 캐시 상태와 revalidate 타이머를 관리합니다. 이는 파일 시스템 기반 또는 In-Memory 캐시 형태로 구현될 수 있습니다.
  • 재생성 큐(Revalidation Queue): 여러 사용자가 동시에 만료된 페이지를 요청할 경우, 서버는 중복된 재생성 작업을 방지하기 위해 재생성 요청을 큐(queue)에 넣고 순차적으로 처리하거나, 첫 번째 요청에 대해서만 재생성을 트리거합니다.
  • HTTP 캐싱 헤더 + 내부 캐시 시스템: CDN과 브라우저의 HTTP 캐싱 헤더(예: Cache-Control)와 Next.js 서버의 내부 캐시 시스템이 복합적으로 작동하여 콘텐츠 제공 및 갱신을 관리합니다.
  • Atomic Deployment: 새로운 HTML 파일이 완전히 생성될 때까지 이전 버전의 HTML을 제공함으로써, 일관성 없는 상태 노출을 방지합니다.

✅ 장점 (The Best of Both Worlds)

  • 빠른 초기 응답 + 주기적 최신화: SSG의 빠른 초기 로딩 속도와 CDN 이점을 유지하면서, SSR처럼 주기적으로 데이터를 최신화할 수 있습니다. 이는 대부분의 동적 콘텐츠 웹사이트에 적절한 **타협점(trade-off)**을 제공합니다.
  • 서버 부하 분산: SSR처럼 매 요청마다 렌더링하지 않고, revalidate 주기 또는 필요에 따라 백그라운드에서만 재생성하므로 서버 부하를 효율적으로 분산시킬 수 있습니다.
  • CDN 활용도 높음: 초기 요청은 물론, revalidate 주기 동안에도 CDN을 통해 캐시된 콘텐츠를 제공하므로 CDN 활용도가 높습니다.
  • 대형 커머스 플랫폼에 적합: 상품 페이지처럼 자주 변경되지는 않지만, 데이터 신선도가 어느 정도 중요한 경우 (예: 상품 가격, 재고)에 매우 효과적입니다.

❌ 단점 (Complexity & Consistency Challenges)

  • 일관성 문제 발생 가능성: 사용자가 만료된 콘텐츠를 보는 동안 백그라운드에서 새로운 콘텐츠가 생성되므로, **'오래된 HTML'과 '새로운 데이터' 간의 불일치(mismatch)**가 일시적으로 발생할 수 있습니다. 이는 특히 API 응답이 페이지 렌더링 로직에 깊이 연관될 때 더욱 복잡해질 수 있습니다.
  • 최초 재생성 시점에 CPU 부하 집중: revalidate 주기가 짧거나 동시에 많은 페이지가 만료되어 재생성될 경우, 특정 시점에 서버의 CPU 부하가 집중될 수 있습니다.
  • 운영 복잡도 증가: SSG보다 복잡한 캐싱 전략과 백그라운드 렌더링 메커니즘을 이해하고 관리해야 하므로 운영 복잡도가 다소 증가합니다.
  • revalidate 시간 설정의 중요성: revalidate 값을 너무 짧게 설정하면 SSR과 유사하게 서버 부하가 증가하고, 너무 길게 설정하면 데이터 신선도가 떨어질 수 있어 적절한 균형점을 찾는 것이 중요합니다.

📊 SSR / SSG / ISR 비교 – 시스템 관점

항목SSRSSGISR
렌더링 시점 요청 시 빌드 시 빌드 + 주기적 (재생성 시)
초기 로딩 속도 느림 (TTFB 길 수 있음) 매우 빠름 (CDN 캐시) 빠름 (초기 SSG와 동일)
데이터 신선도 항상 최신 정적 (빌드 시점) TTL 기반 (최대 revalidate 시간)
서버 부하 매우 높음 없음 낮음 (재생성 시만 부하)
CDN 캐싱 전략 거의 불가 CDN 정적 캐시 TTL 캐시 + 백그라운드 재생성 큐
CDN 활용도 X O O (조건부)
보안/세션 반영 가능 불가능 제한적 (빌드 시점 데이터는 불가)
운영 복잡도 간단 (단일 서버) 매우 간단 (정적 호스팅) 복잡 (캐싱, 재생성 관리 필요)

 


 

🔬 메모리/CPU 자원 비교 시나리오

컴퓨터 과학의 관점에서 자원 활용 효율성을 살펴보겠습니다.

 

10,000 유저 동시 요청 서버 CPU 및 메모리 급상승 (각 요청 렌더링) 캐시된 응답 제공, 재생성 큐 분산 (부하 관리) 모두 정적 처리로 안정적, 서버 자원 거의 0
데이터가 10분마다 변경 부하↑ (매 요청마다 렌더링) revalidate: 600 으로 대응 (효율적 갱신) 불가능 (재빌드 필요, 비효율적)
빌드 시간 1시간 소요 프로젝트 해당 없음 (런타임 렌더링) 초기 빌드 후 효율적 갱신 (증분) 전체 재빌드 필요 (매우 비효율적)
 

🧠 설계 시 고려 포인트: 아키텍처적 의사결정

어떤 렌더링 전략을 선택할지는 단순히 기술적 선호를 넘어, 서비스의 본질적인 요구사항과 제약 조건을 깊이 있게 분석해야 하는 **아키텍처적 의사결정(architectural decision)**입니다.

  • 페이지에 유저 세션/로그인 여부가 반영되는가? (개인화된 콘텐츠의 필요성)
  • 페이지 데이터는 변경 주기/빈도가 어떻게 되는가? (데이터 신선도 요구사항)
  • 대규모 트래픽을 처리해야 하는가? (확장성 및 부하 분산 전략)
  • 글로벌 사용자 대상일 경우 CDN 활용이 중요한가? (전역 성능 및 지연 시간)
  • 빌드 시간이 긴 대형 프로젝트에서 전체 SSG는 현실적인가? (개발 생산성 및 배포 워크플로우)
  • 일시적인 데이터 불일치를 허용할 수 있는가? (ISR의 trade-off 수용 여부)
  • 웹 페이지의 상호작용 수준은 어느 정도인가? (JavaScript 번들의 크기와 Hydration 비용)

 

📌 정리: 언제 어떤 방식을 쓰는가?

페이지 특성권장 렌더링 방식상세 설명
유저 개인화, 로그인 정보 포함 (동적 콘텐츠) SSR 사용자별로 달라지는 대시보드, 장바구니, 개인 설정 페이지 등 데이터 신선도가 높고 개인화가 필수적인 경우에 적합합니다.
마케팅 랜딩 페이지, 블로그, 문서 (정적 콘텐츠) SSG 콘텐츠가 자주 변경되지 않고, 최고 수준의 로딩 속도와 SEO가 중요한 경우에 적합합니다. 블로그 게시물, 회사 소개 페이지, 약관 등.
상품 목록, 게시판 글 등 자주 바뀌지만 실시간 아님 ISR SSG의 성능 이점을 취하면서도, 데이터 변경에 유연하게 대응해야 하는 경우에 적합합니다. 쇼핑몰 상품 상세 페이지, 뉴스 기사, 이벤트 목록 등 초기 로딩은 빠르지만, 주기적으로 최신 정보를 반영해야 할 때 유용합니다.
실시간 주식 시세, 채팅 애플리케이션 (실시간성) CSR + WebSocket 순수 **클라이언트 사이드 렌더링(CSR)**에 WebSocket 또는 Server-Sent Events(SSE)와 같은 실시간 통신 프로토콜을 결합하는 것이 더 적합합니다. 이는 렌더링 전략의 범주를 넘어서는 실시간 데이터 동기화 문제입니다.
 

✍️ 마무리: 현대 웹 개발자의 설계 역량

SSR, SSG, ISR은 단순히 "빨라서 좋다"는 단편적인 문제가 아니라, 웹 애플리케이션의 아키텍처 설계, 시스템 자원 활용 효율성, 사용자 경험(UX), 검색 엔진 최적화(SEO), 그리고 CDN 활용 전략 모두를 종합적으로 고려해야 하는 복합적인 문제입니다.

모던 웹 개발자에게 요구되는 역량은 개별 페이지의 특성과 서비스의 비즈니스 요구사항을 면밀히 분석하여, 이 세 가지 렌더링 전략을 하이브리드(hybrid) 방식으로 적절히 조합하고 최적화하는 능력입니다. 이는 개발 비용, 운영 비용, 그리고 사용자 만족도라는 다차원적인 목표를 동시에 달성하기 위한 핵심적인 설계 역량이라 할 수 있습니다.

 

728x90
반응형