Skip to content

[2팀 정도은] Chapter 4-1 성능 최적화 🧦🧦🧦🧦🧦#39

Open
nemobim wants to merge 22 commits intohanghae-plus:mainfrom
nemobim:main
Open

[2팀 정도은] Chapter 4-1 성능 최적화 🧦🧦🧦🧦🧦#39
nemobim wants to merge 22 commits intohanghae-plus:mainfrom
nemobim:main

Conversation

@nemobim
Copy link

@nemobim nemobim commented Sep 4, 2025

과제 체크포인트

배포 링크

바닐라: https://nemobim.github.io/front_6th_chapter4-1/vanilla/
리액트: https://nemobim.github.io/front_6th_chapter4-1/react/

기본과제 (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 (상품 상세 페이지들)
  • 빌드 타임 페이지 생성
  • 파일 시스템 기반 배포

구현 과정 돌아보기

가장 어려웠던 부분과 해결 과정

전체적으로 다 어려웠습니다..

  1. HTML을 만든다.
  2. 서버에서 해당 경로에 접속시 기존에 데이터를 가져와서 같이 넣어주고 HTML을 뿌린다
  3. CSR/SSR을 사용하는 부분을 조건부 처리해서 서버 환경에서도 돌아가도록 만든다.

라고 처음에 생각했으나 결국 연결고리를 짓는 부분이 부족한지 how 에서 막혔습니다.

왜 SSR이 안 되지?

어찌저찌 가이드를 보고 따라 가봤을때는 어떤식으로 동작하는지 콘솔도 찍어보고 테스트 해봤습니다.
그런데 코드를 구현해도 SSR이 아주 잠깐 나왔다가 사라지고 CSR 코드가 나오더라구요,,

서버: <h1>🛍️ 쇼핑몰</h1>
↓ (JavaScript 로드 후)
클라이언트: <div>기존 쇼핑몰</div>

처음에는 버그인줄 알았으나 코드를 미구현해서 그랬고, 하이드레이션 과정을 빼먹었다는 걸 알게 되었습니다.

1. SSR ≠ 단순 HTML 생성
잘못된 이해: "서버에서 HTML만 만들면 SSR 끝"
올바른 이해: "서버 HTML + 클라이언트 Hydration = 완전한 SSR"
2. Hydration의 진짜 의미
잘못된 이해: "클라이언트에서 다시 렌더링하기"
올바른 이해: "서버 HTML을 살려두고 JavaScript 기능만 붙이기"

어쩔때는 성공하고 어쩔때는 실패하는 e2e

싱글톤 라우터로 인한 동시성 문제의 심각성을 깨달았습니다. 라우터를 하나의 인스턴스로 만들어놨더니 여러 요청이 동시에 들어올 때 서로 다른 요청의 URL이 뒤섞이는 문제가 발생했습니다...클라이언트에서는 한 사용자당 하나의 라우터로 충분하지만 서버에서는 각 요청마다 별도의 라우터 인스턴스를 생성해야 상태가 섞이지 않는다는 걸 배웠습니다.

구현하면서 새롭게 알게 된 개념

  • SSR과 SSG의 차이점

    • SSR은 요청할 때마다 서버에서 실시간으로 HTML을 생성
    • SSG는 빌드 타임에 미리 모든 페이지를 정적 파일로 만들어두는 방식
  • Universal JavaScript 패턴

    • 같은 코드가 서버와 클라이언트에서 다르게 동작해야 한다.
    • typeof window === "undefined" 체크가 코드 곳곳에 필요함
    • localStorage나 DOM API 같은 브라우저 전용 기능들을 서버에서 대체 필요
  • ** useSyncExternalStore의 getServerSnapshot**

    • React 18에서 서버-클라이언트 상태 동기화의 핵심
    • 세번째 인자로 서버 환경에서의 초기 상태를 제공해야 Hydration 불일치를 방지

성능 최적화 관점에서의 인사이트

SSG로 생성된 정적 파일들을 서버 처리 과정 없이 바로 HTML 파일을 전송할 수 있어서 응답 시간이 빠르다!
SSR로 초기 데이터를 미리 받으니까 사용자 경험에 좋다!

학습 갈무리

Q1. 현재 구현한 SSR/SSG 아키텍처에서 확장성을 고려할 때 어떤 부분을 개선하시겠습니까?

  • 요청별 상태 격리 시스템 구축
    • 현재 싱글톤 라우터로 인한 동시성 문제를 해결하기 위해 요청별로 독립적인 컨텍스트를 만드는 시스템이 필요
    • 각 요청마다 격리된 라우터, 스토어 인스턴스를 생성하도록 개선

Q2. Express 서버 대신 다른 런타임(Cloudflare Workers, Vercel Edge Functions 등)을 사용한다면 어떤 점을 수정해야 할까요?

모르겠어요...AI 돌렸습니다.

  • 파일 시스템 접근 제거:
    현재 템플릿 파일을 fs.readFile로 읽어오는데, 엣지 환경에서는 파일 시스템이 없으므로 템플릿을 환경변수나 KV 스토리지에 저장하거나, 번들에 인라인으로 포함시켜야 합니다.
  • Cold Start 최적화:
    번들 크기를 최소화하기 위해 dynamic import를 적극 활용하고, 자주 사용되는 코드만 메인 번들에 포함시키겠습니다. 또한 워커 인스턴스 간 상태 공유가 불가능하므로 모든 상태를 요청 스코프로 관리해야 합니다.
  • Web API 호환성:
    Node.js 전용 모듈들(fs, path 등)을 Web API 기반으로 대체해야 합니다. Buffer 대신 Uint8Array, require 대신 import를 사용하고, 서버 환경 감지도 typeof process !== 'undefined' 방식으로 변경해야 합니다.

Q3. 현재 구현에서 성능 병목이 될 수 있는 지점은 어디이고, 어떻게 개선하시겠습니까?

  • renderToString CPU 병목:
    • 현재 동기적 렌더링으로 인해 CPU 사용량이 높은데 Worker Thread를 활용한 병렬 렌더링이나 렌더링 결과 캐싱을 도입
      -자주 요청되는 페이지는 Redis에 렌더링 결과를 저장하여 CPU 부하를 줄이기
  • 메모리 누수 방지:
    • 싱글톤 패턴으로 인한 메모리 누적 문제를 해결하기 위해 요청 완료 후 명시적으로 인스턴스를 정리하는 가비지 컬렉션 트리거를 추가
    • 대용량 데이터 처리 시 스트리밍 방식을 도입하여 메모리 사용량을 제어
  • 정규식 라우터 최적화

Q4. 1000개 이상의 상품 페이지를 SSG로 생성할 때 고려해야 할 사항은 무엇입니까?

  • 메모리 사용량 관리
    • 한 번에 1000개를 다 만들려고 하면 컴퓨터 메모리가 부족하다.
    • 100개씩 나누어서 만들고, 100개 만들 때마다 메모리를 비워주는 방식으로 진행
    • 순차 처리 방식을 Worker Thread 기반 병렬 배치 처리로 개선
  • 증분 빌드 시스템
    • 변경된 상품만 재생성하는 시스템을 구축
    • 상품 데이터의 해시값을 저장해두고 변경 감지 시에만 해당 페이지를 재빌드하여 전체 빌드 시간을 대폭 단축
  • CDN 캐시 무효화 전략
    • 상품별로 태그를 부여하여 특정 상품 변경 시 해당 페이지만 선별적으로 캐시를 무효화

Q5. Hydration 과정에서 사용자가 느낄 수 있는 UX 이슈는 무엇이고, 어떻게 개선할 수 있을까요?

  • 인터랙션 차단 문제
    • 전체 앱이 Hydration 완료될 때까지 모든 버튼이 비활성화
    • React 18의 Selective Hydration을 도입하여 우선순위가 높은 컴포넌트(장바구니 버튼, 검색 등)부터 순차적으로 활성화 필요
  • 로딩 상태 시각화
    • Hydration 진행 상태를 사용자가 알 수 있도록 스켈레톤 UI와 프로그레시브 로딩 인디케이터를 추가

Q6. 이번 과제에서 학습한 내용을 실제 프로덕션 환경에 적용할 때 추가로 고려해야 할 사항은?

  • 얼마나 빠른지, 에러가 얼마나 나는지 계속 지켜보기.. 모니터링
  • window.__INITIAL_DATA__에 사용자 입력 데이터가 포함될 때 XSS 공격을 방지하기 위한 HTML 이스케이핑과 데이터 sanitization이 필수
  • Content Security Policy(CSP) 설정으로 외부 스크립트 실행을 제한하고 서버 에러 시 스택 트레이스나 민감한 정보가 클라이언트에 노출되지 않도록 에러 응답을 필터링

Q7. Next.js 같은 프레임워크 대신 직접 구현한 SSR/SSG의 장단점은 무엇인가요?

장점 - 내 마음대로 할 수 있음:
원하는 대로 정확히 만들 수 있습니다. 그리고 문제 생겼을 때 어디가 잘못됐는지 알 수 있어요.

단점 - 너무 힘들고 시간 많이 걸림:
개발 및 유지보수 비용이 매우 높습니다. Next.js가 자동으로 처리해주는 코드 스플리팅, 이미지 최적화, 자동 Static 최적화 등을 모두 직접 구현해야 하고 새로운 React 버전이나 웹 표준 변화에 대응하는 부담도 큼...

결론: 학습 목적이나 매우 특수한 요구사항이 있는 경우가 아니라면 Next.js 같은 검증된 프레임워크를 사용하는 것이 좋다.
다만 이번 구현 경험을 통해 Next.js를 사용할 때도 내부 동작을 이해하고 더 효과적으로 활용할 수 있게 된거같다(?)

Q8. Next.js 를 이용하여 SSG 방식으로 배포하려면 어떻게 해야 좋을까요?

  • 인기 상품만 미리 만들고 나머지는 사용자가 처음 방문할 때 만들도록 설정
  • 나머지는 fallback: 'blocking'으로 설정하여 사용자가 처음 방문할 때 만들도록 한다.
  • ISR(Incremental Static Regeneration) 활용
    • 옵션을 설정하여 페이지를 주기적으로 업데이트할 수 있다. 상품 정보는 하루에 한 번, 가격 정보는 1시간마다 갱신

코드 품질 향상

자랑하고 싶은 구현

솔직히 자랑할 만한 코드는 없었습니다... 과제 완성에 급급해서 "일단 돌아가게만 만들자"는 마음으로 구현했습니다..

개선하고 싶은 부분

  • 싱글톤 패턴 라우터의 동시성 문제:
    -가장 큰 문제였던 싱글톤 라우터를 요청별 인스턴스로 변경

    • 현재는 여러 요청이 들어올 때 라우터 상태가 뒤섞여서 A 사용자가 B 사용자의 페이지를 보는 심각한 버그가 발생합니다.
  • any 타입 남발:

    • 시간에 쫓겨서 타입 정의를 제대로 하지 못하고 any로 때려박은 부분들이 많다
  • 중복된 Mock 코드:

  • 기존 API와 동일한 인터페이스를 유지하기 위해 serverMock을 따로 만들었는데 코드 중복이 심하고 유지보수가 어려움

  • 추상화를 통해 공통 로직 뽑아내기

리팩토링 계획

  • 과제 하느라 촉박해서 타입을 any 로 때려박은 부분이있음
  • mock을 사용하기 위해 기존코드와 동일한 serverMock을 따로 만듦..

학습 연계

다음 학습 목표

있는거나 잘하겠습니다..!

실무 적용 계획

CSR며이며 데이터가 잘 바뀌지 않는 프로젝트를 찾아 SSR로 처리해보자..1

리뷰 받고 싶은 내용

  • 서버와 클라이언트에서 같은 코드를 실행하면서 환경별 분기 처리가 너무 많아졌는데, 더 깔끔하게 추상화할 수 있는 패턴이 있는지 궁금합니다.
  • 학습연계로 뭘하면 좋을까요

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