Skip to content

[2팀 이정민] Chapter 4-1 성능최적화: SSR, SSG, Infra#31

Open
LEE-jm96 wants to merge 7 commits intohanghae-plus:mainfrom
LEE-jm96:main
Open

[2팀 이정민] Chapter 4-1 성능최적화: SSR, SSG, Infra#31
LEE-jm96 wants to merge 7 commits intohanghae-plus:mainfrom
LEE-jm96:main

Conversation

@LEE-jm96
Copy link

@LEE-jm96 LEE-jm96 commented Dec 17, 2025

과제 체크포인트

배포 링크

vanilla: https://lee-jm96.github.io/front_7th_chapter4-1/vanilla/

기본과제 (Vanilla SSR & SSG)

Express SSR 서버

  • Express 미들웨어 기반 서버 구현
  • 개발/프로덕션 환경 분기 처리
  • HTML 템플릿 치환 (<!--app-html-->, <!--app-head-->)

서버 사이드 렌더링

  • 서버에서 동작하는 Router 구현
  • 서버 데이터 프리페칭 (상품 목록, 상품 상세)
  • 서버 상태관리 초기화

클라이언트 Hydration

  • window.__INITIAL_DATA__ 스크립트 주입
  • 클라이언트 상태 복원
  • 서버-클라이언트 데이터 일치

Static Site Generation

  • 동적 라우트 SSG (상품 상세 페이지들)
  • 빌드 타임 페이지 생성
  • 파일 시스템 기반 배포

심화과제 (React SSR & SSG)

React SSR

  • renderToString 서버 렌더링
  • TypeScript SSR 모듈 빌드
  • Universal React Router (서버/클라이언트 분기)
  • React 상태관리 서버 초기화

React Hydration

  • Hydration 불일치 방지
  • 클라이언트 상태 복원

Static Site Generation

  • 동적 라우트 SSG (상품 상세 페이지들)
  • 빌드 타임 페이지 생성
  • 파일 시스템 기반 배포

아하! 모먼트 (A-ha! Moment)

처음에는 SSR과 CSR의 차이를 그저 렌더링이 서버에서 되느냐, 브라우저에서 되느냐 정도로만 이해하고 있었습니다. 그래서 서버에서 HTML을 내려주면 클라이언트는 그냥 그 위에서 다시 시작하는 것이라고 막연히 생각했습니다. 그런데 구조를 하나씩 따라가다 보니 문제는 렌더링 위치가 아니라 상태의 출발점이라는 걸 알게 되었습니다. 서버에서는 이미 페이지에 필요한 데이터를 모두 프리페칭하고 있는데, 클라이언트가 시작할 때 그 사실을 전혀 모르고 다시 “빈 상태”에서 API 요청을 반복하고 있었습니다. 이때 window.__INITIAL_DATA__의 역할이 보이기 시작했습니다. 서버에서 만든 데이터를 HTML에 그대로 주입해 두고, 클라이언트는 hydration 과정에서 그 값을 읽어 스토어 상태를 복원합니다.

이 과정을 거치면서 클라이언트는 서버와 완전히 같은 상태에서 출발하게 되고, 불필요한 재요청도, 상태 불일치도 사라지는 것을 알게 됐습니다.
이제 보니 SSR → CSR 전환은 새로 렌더링을 시작하는 과정이 아니라 서버에서 만들어진 상태를 이어받는 과정이었습니다.

결국 SSR의 핵심은 서버에서 UI를 미리 그리는 것이 아니라 서버에서 준비한 상태를 클라이언트가 그대로 이어서 사용하는 것이라는 걸 이번 구조를 통해 체감하게 되었습니다.

자유롭게 회고하기

처음에는 SSR과 CSR의 차이를 정말 단순하게만 생각하고 있었습니다.
<div id="root"></div>안에 처음부터 내용이 있느냐, 없느냐 그 정도의 차이라고만 받아들이고 있었던 것 같습니다.
CSR은 비어 있는 root에 자바스크립트가 실행된 뒤에 화면이 그려지고, SSR은 서버에서 이미 그린 HTML이 들어 있으니 “아, 그래서 SSR이 빠르구나” 정도로 이해하고 있었습니다. 그런데 구조를 따라가면서 보니 이 차이는 원인이 아니라 결과에 가깝다는 걸 알게 되었습니다.

SSR에서 #root 안에 내용이 들어 있는 이유는 단순히 서버에서 미리 렌더링했기 때문이 아니라, 서버가 이미 데이터를 알고 있는 상태로 렌더링을 했고, 그 상태를 클라이언트가 그대로 이어받기 때문이었습니다.

반대로 CSR은 HTML도 비어 있고, 상태도 비어 있는 상태에서 클라이언트가 모든 걸 처음부터 다시 시작합니다.
이 지점에서 깨달은 건 SSR과 CSR의 차이가 “HTML에 뭐가 들어 있느냐”의 문제가 아니라, 클라이언트의 시작 상태가 다르다는 점이었습니다.

SSR은 서버에서 만들어진 화면과 상태를 클라이언트가 끊김 없이 이어받는 구조이고, CSR은 처음부터 끝까지 클라이언트가 혼자 만들어가는 구조였습니다. 그래서 결국 <div id="root"></div>안에 첫 렌더링 때 내용이 있느냐 없느냐는 SSR과 CSR을 구분해주는 가장 눈에 띄는 차이일 뿐, 그 뒤에는 “상태를 이어받느냐, 다시 시작하느냐” 라는 훨씬 본질적인 차이가 숨어 있다는 걸 이번에야 제대로 이해하게 되었습니다.

리뷰 받고 싶은 내용

실무 환경에서도 서버 사이드 렌더링 과정에서 서버가 미리 프리페칭한 데이터를 window.__INITIAL_DATA__와 같은 전역 객체에 주입한 뒤,
클라이언트가 hydration 단계에서 해당 데이터를 읽어 초기 스토어 상태를 복원하는 방식이 일반적으로 사용되는지 궁금합니다.

또한 이러한 전역 데이터 주입 방식이 SSR과 CSR 간 상태 불일치를 방지하기 위한 표준적인 패턴인지, 아니면 Next.js나 특정 프레임워크 내부 구현에 가까운 방식인지도 함께 알고 싶습니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant