[9팀 권지호] Chapter 4-1 성능 최적화#4
Open
tomatopickles404 wants to merge 24 commits intohanghae-plus:mainfrom
Open
Conversation
This comment was marked as resolved.
This comment was marked as resolved.
Author
열심히 pr 작성중입니다 주닐님ㅠㅠ ... |
- renderToString을 이용한 서버사이드 렌더링 - SSR 전용 컴포넌트들 (SSRHomePage, SSRProductDetailPage, SSRNotFoundPage) - 상품 데이터 필터링 및 정렬 로직 - 카테고리 추출 및 검색 기능 - MSW 서버 설정 (node.ts) - 동적 메타태그 생성
- BaseRouter: 공통 라우터 로직 추상화 - ServerRouter: 서버사이드 라우터 구현 - Router: 클라이언트사이드 라우터 개선 - 서버/클라이언트 분기 처리 - 라우터 팩토리 패턴 적용
- SSR 초기 데이터 하이드레이션 - window.__INITIAL_DATA__를 통한 클라이언트 상태 복원 - 홈페이지/상품상세 페이지 데이터 분기 처리 - 로딩 상태 및 상태 관리 초기화 - Hydration 불일치 방지
- Express 기반 SSR 서버 구현 - Vite 미들웨어 통합 (개발/프로덕션 분기) - MSW 서버 설정 및 API 모킹 - HTML 템플릿 주입 및 초기 데이터 스크립트 생성 - 압축 및 정적 파일 서빙 설정
- 동적 라우트 SSG (상품 상세 페이지들) - 빌드 타임 페이지 생성 - 주요 상품 ID 목록 기반 정적 파일 생성 - HTML 템플릿 주입 및 초기 데이터 스크립트 생성 - 파일 시스템 기반 배포 준비 - 폴백 페이지 생성 로직
- React 라우터 개선 및 최적화 - TypeScript 설정 업데이트 - 라우터 팩토리 패턴 적용 - 순환 의존성 해결
fbc1a58 to
ab18b2c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
과제 체크포인트
배포 링크
기본: https://tomatopickles404.github.io/front_6th_chapter4-1/vanilla/
심화: https://hanghae-plus.github.io/front_6th_chapter4-1/react/
기본과제 (Vanilla SSR & SSG)
Express SSR 서버
<!--app-html-->,<!--app-head-->)서버 사이드 렌더링
클라이언트 Hydration
window.__INITIAL_DATA__스크립트 주입Static Site Generation
심화과제 (React SSR & SSG)
React SSR
renderToString서버 렌더링React Hydration
Static Site Generation
구현 과정 돌아보기
가장 어려웠던 부분과 해결 과정
발제부터 2번 들었습니다. 가장 어려웠던 부분이라기 보다는.. 매 순간이 어려웠습니다.
제가 이해했다고 생각하고 코드를 작성하고 확인하면 매 순간 제 예상과 다른 결과였고, 이를 디버깅 하는 것에 대해 감을 잡는게 어려웠습니다.
제가 어려워하는 이유는 원리를 제대로 이해하고 있지 못하고 있기 때문이라고 생각했고, 어떤 부분을 이해하지 못하는 것인지 파악하기 위해 서버사이드 렌더링 흐름과 프로젝트 구조에 대한 파악을 해야겠다고 생각했습니다.
먼저, 브라우저의 서버 사이드 렌더링의 흐름을 한 번 정리하고 프로젝트의 구조를 이해해보고자 했습니다.
그리고 작성해야 하는 각 파일이 그 흐름 중 어디에서 동작하는지, 어떤 역할을 하는지 분석했습니다.
SSR 기본 흐름
진입점 파일들
vanilla 프로젝트 흐름
1. 서버 시작 (server.js)
2. 사용자 접근
3. 서버사이드 렌더링 상세 과정
4. 클라이언트 하이드레이션
JavaScript 로드 후 상태 복원 및 SPA 활성화
[하이드레이션 상세 과정]
5. 반응형 상태관리
사용자 액션 → Store 변경 → Observer 알림 → 자동 렌더링
6. SPA 라우팅
이후 네비게이션은 CSR로 처리 (페이지 새로고침 없음)
이렇게 구조와 함께 흐름을 정리 하고 난 뒤에는 과제의 요구사항에 대한 이해가 되었습니다.
같은 이름의 함수가 두 곳에 있어서 혼란스러웠는데, 각 역할이 명확히 구분됨을 이해했습니다.
main.js와 main-server.js에 대해 모호했던 것들에 대한 이해가 되었고, 디버깅 할 때도 서버 렌더링 문제인지, 클라이언트 하이드레이션 관련 문제인지를 구분할 수 있었습니다.
구현하면서 새롭게 알게 된 개념
서버사이드에서의 인스턴스 생성 전략
SSR 환경은 클라이언트/서버에 맞게 각각 다른 라우터를 써야 하는데, 이러한 환경에서 팩토리 패턴은 유용할 수 있습니다.
[현재]
라우터를 싱글톤으로 생성하여 사용하고 있습니다.
[발생한 문제들]
여러 테스트가 동시에 같은 라우터 인스턴스를 공유하여 net::ERR_CONNECTION_REFUSED 및 상태 오염 발생했습니다.
근본적인 원인은 동시성 문제였습니다.
여러 요청이 하나의 라우터 인스턴스 상태를 공유하면서 발생한 문제였습니다.
[동시성 문제가 발생하는 원리]
Node.js는 싱글 스레드 이벤트 루프로 동작하지만, 비동기 요청들은 동시에 처리됩니다.
문제는 다음과 같은 상황에서 발생했습니다.
[실행 순서]
결과적으로,
/products가 아닌/로 라우팅근본적인 원인은 상태를 가진 객체를 여러 비동기 요청이 공유하면서 발생하는 전형적인 동시성 문제였습니다.
개선을 시도해볼 수 있는 방법은 여러가지가 있었습니다.
서버사이드에서 라우터 인스턴스를 개별 생성한다.
서버사이드에서 msw를 사용하는 비동기 이슈를 발생시키지 않고, 동기적으로 목데이터를 활용하여 데이터를 호출한다.
저는 라우터 인스턴스를 개별 생성하는 것이 더 적절하다고 생각했지만, 병목이 발생하여 과제에는 목데이터를 사용하여 해결했습니다.
따라서 개선 방안에는 라우터 인스턴스를 생성하여 해결하는 과정을 정리해보았습니다.
[개선 방안]
Next.js, Nuxt.js와 같은 프레임워크들이 요청별로 독립적인 컨텍스트를 생성하는 이유이기도 합니다.
Next.js에서 Tanstack Query의 인스턴스를 사용할 때, 왜 SSR에서는 매번 인스턴스를 생성해야 하는지에 대해 생각해보지 않았었는데 이번 과제를 통해 근본적인 원인을 알아가게 되었습니다.
학습 갈무리
Q1. 현재 구현한 SSR/SSG 아키텍처에서 확장성을 고려할 때 어떤 부분을 개선하시겠습니까
1. 라우터 확장성 개선
SSR 환경은 클라이언트/서버에 맞게 각각 다른 라우터를 써야 하는데, 이러한 환경에서 팩토리 패턴은 유용할 수 있습니다.
[현재]
라우터를 싱글톤으로 생성하여 사용하고 있습니다.
[개선 방안]
[개선 효과]
2. 캐싱 전략 개선
[현재]
현재는 매 요청마다 서버에서 렌더링을 수행하고 있습니다.
[개선 방안]
계층별 캐싱 전략을 적용해 성능과 확장성을 개선할 수 있습니다.
[개선 효과]
Q2. Express 서버 대신 다른 런타임(Cloudflare Workers, Vercel Edge Functions 등)을 사용한다면 어떤 점을 수정해야 할까요?
Express는 전통적인 Node.js 서버 환경에서 동작하는 반면, Cloudflare Workers나 Vercel Edge Functions 같은 서버리스 환경은 여러 환경에서 호환되는 웹 표준 API을 기반으로 동작합니다.
따라서 다른 런타임에서도 동작하기 위해서는 Node.js에 의존적인 부분을 웹 표준 API를 기반으로 변경해야 합니다.
수정이 필요한 부분들은 아래와 같습니다.
1. 서버 초기화 방식 변경
[Express]
[서버리스 환경으로 수정]
2. 파일 시스템 접근 제거
[기존 문제가 되는 코드]
[서버리스 대응으로 수정]
3. 상태 관리 방식 변경
[Express]
[외부 스토리지로 수정]
4. 요청/응답 처리 방식 수정
[Express]
[Web API 표준으로 수정]
이렇게 Node.js 런타임에서 동작하는 부분을 웹 표준을 준수하는 코드로 전환하는 것이 핵심입니다.
Q3. 현재 구현에서 성능 병목이 될 수 있는 지점은 어디이고, 어떻게 개선하시겠습니까?
[문제점]
개발 환경에서 매 요청마다 파일을 읽고 모듈을 로드하고 있습니다.
[개선 방안]
Q4. 1000개 이상의 상품 페이지를 SSG로 생성할 때 고려해야 할 사항은 무엇입니까?
SSG는 빌드 타임에 모든 페이지를 미리 생성하여 런타임 성능 최적화와 함께 빠른 응답 속도를 목표로 사용된다고 생각합니다.
따라서 이러한 점에 초점을 맞추어 고려해본다면 아래와 같이 정리할 수 있습니다.
1. 빌드 시간 최적화
빠른 배포와 지속적인 업데이트를 위해서는 빌드 시간이 관리 가능한 범위 내에 있어야 합니다.
[병렬 처리]
[증분 빌드]
전체 1000개 상품 중 실제로 변경되는 것은 보통 5-10% 내외입니다. 변경되지 않은 990개 페이지를 매번 재생성하는 것은 비효율적입니다.
따라서,
2. 메모리 사용량 관리
[메모리 관리가 필요한 이유]
[개선 방안]
스트리밍 방식으로 데이터 처리
3. CDN 캐시 무효화 전략
상품 하나가 변경될 때마다 전체 CDN 캐시를 무효화한다면 다음과 같은 문제점이 발생할 수 있습니다.
[선택적 무효화]
[개선 효과]
Q5. Hydration 과정에서 사용자가 느낄 수 있는 UX 이슈는 무엇이고, 어떻게 개선할 수 있을까요?
Hydration 과정에서 사용자가 느낄 수 있는 UX 이슈는 다음과 같습니다.
1. 깜빡임 현상 (Flash of Unstyled Content)
SSR로 생성된 HTML과 클라이언트에서 하이드레이션된 결과가 일치하지 않을 때 발생합니다.
[개선 방안]
2. 상호작용 불가 기간 (Uncanny Valley)
사용자는 버튼이나 링크를 볼 수 있지만 클릭해도 반응하지 않는 기간이 존재합니다.
[개선 방안]
3. 레이아웃 시프트 (Layout Shift)
동적 콘텐츠 로드 시 페이지 레이아웃이 갑자기 변경되는 현상입니다.
[개선 방안]
Q6. 이번 과제에서 학습한 내용을 실제 프로덕션 환경에 적용할 때 추가로 고려해야 할 사항은?
현재 에러 로깅이 부족하여 프로덕션 환경에서는 해당 부분이 보완되어야 합니다.
사용자에게 적절한 에러 메세지 제공도 부족합니다.
[개선 방안]
서버, 클라이언트 사이드에 맞는 에러 핸들링을 강화하고 에러 바운더리 및 폴백 UI를 적용합니다.
Q7. Next.js 같은 프레임워크 대신 직접 구현한 SSR/SSG의 장단점은 무엇인가요?
[장점]
[단점]
Q8. Next.js 를 이용하여 SSG 방식으로 배포하려면 어떻게 해야 좋을까요?
App Router 방식에서는 dynamic 옵션을 export하여 렌더링 방식을 제어할 수 있습니다.
1. SSG 페이지 생성
이 밖에도 다양한 dynamic 옵션들이 있습니다. 기본값은 auto입니다.
2. 빌드
next.config.js 설정
standalone 모드란?
언제 사용하는가?
3. 배포
정적 호스팅으로 배포합니다.
코드 품질 향상
개선하고 싶은 부분
라우터 방식 변경
앞서 언급했던 라우터 인스턴스 생성 방식을 클라이언트/서버에 맞는 방식으로 개선하여 동시성 문제를 해결하고자 합니다.
App에서 fallback 처리
[문제] CSR 빌드 서버에서의 빈 화면
React 패키지에서 빌드한 서버를 구현하는 것에 병목이 가장 심했습니다.
로컬 서버에서는 csr이 잘 렌더링 되는데 빌드한 서버(preview)에서는 네트워크는 이상이 없고 빈 화면이 나와 원인을 디버깅하기가 어려웠습니다. 제가 알아낸 결론은 렌더링할 컴포넌트가 포함되지 않은 상태로 빌드되어 빈 화면이 나오는 것이라고 판단했고, 현재는 csr 테스트코드 통과를 위해 App에서 컴포넌트를 분기처리를 하는 방식으로 테스트 코드를 통과 시켰습니다.
이 방식은 적절한 방식은 아니라고 생각되기도 하고, 제가 파악한 원인도 정확한 원인이 아닐 수 있을 것 같아서 이부분에 대한 원인을 찾아서 해결하고 싶습니다.
[축약한 현재 구조]
학습 연계
다음 학습 목표
구현하면서 알게 되었던 동작 원리나 개선 포인트들을 학습하면서 Next.js에서는 이런 점들을 어떻게 풀어냈을지 궁금해졌습니다. Next.js가 제공하는 렌더링 최적화 전략들(스트리밍이나 Partial Prerendering와 같은)이나 구현 방식에 대해 마인드맵을 그려보며 복습해보고, 이 과제에서 구현한 방식과 비교해보면 조금 더 정리가 잘 될 것 같습니다.
리뷰 받고 싶은 내용
이 과제를 다시 한 번 수행 한다면 추가로 구현해보면 도움이 될만한 것들을 추천해주실 수 있으실까요?!
과제를 확장해서 같이 경험하면 좋을 것 같은 것이 있다면 추천해주시면 감사하겠습니다!
앞에서 언급한 개선하고 싶은 부분의
에서 언급한 문제의 원인을 알고 싶습니다.
주된 병목은 로컬 서버에서 csr, ssr가 잘 동작한다면 그대로 빌드한 서버에서도 동일하게 화면이 잘 나올 것이라고 예상-> 빈 화면 에서의 디버깅이었습니다. 이런 현상에 대한 원인은 주로 어떤 것인지, 가장 먼저 어떤 부분을 생각해야할까요? 그리고 이런 상황에서 디버깅을 어떤 순서대로 수행해야할 지 감을 잡는 것이 어려웠습니다!