과제 체크포인트
배포 링크
기본 : https://adds9810.github.io/front_6th_chapter4-1/vanilla/ 심화 : https://adds9810.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 (상품 상세 페이지들)
- 빌드 타임 페이지 생성
- 파일 시스템 기반 배포
구현 과정 돌아보기(/vanilla 기준)
가장 어려웠던 부분과 해결 과정
"window is not defined" 에러 해결 과정이 가장 어려웠습니다.
- 문제: 서버 환경에서
window객체에 접근하려고 해서 SSR/SSG 빌드 시 에러 발생 - 해결:
typeof window !== "undefined"조건부 체크로 서버/클라이언트 분기 처리 - 구체적 수정:
createStorage.js에서window.localStorage접근을 조건부로 처리하고,main-server.js와static-site-generate.js에서fs.readFileSync로 직접items.json파일을 읽어 데이터 로딩
서버 라우터와 클라이언트 라우터 동기화 문제
- 문제: 서버에서 렌더링된 HTML과 클라이언트에서 라우팅 결과가 달라서 Hydration 에러 발생
- 해결:
ServerRouter클래스를 별도로 구현하여 서버 환경에 맞는 라우팅 로직 구현 - 구체적 수정:
BaseRouter추상 클래스를 기반으로Router(클라이언트)와ServerRouter(서버)를 분리하여 각 환경에 맞는 라우팅 로직 구현
구현하면서 새롭게 알게 된 개념
SSR과 SSG의 실제 동작 차이를 알게 되었습니다.
- SSR은 요청 시마다 서버에서 HTML을 생성하여 응답
- SSG는 빌드 타임에 미리 모든 페이지의 HTML을 생성하여 정적 파일로 배포
- SSG가 CDN 캐싱에 더 유리하고 초기 로딩 속도가 빠름
Hydration 과정의 중요성을 깨달았습니다.
- 서버에서 렌더링된 HTML과 클라이언트의 Virtual DOM이 픽셀 단위로 일치해야 함
window.__INITIAL_DATA__를 통한 서버 데이터 복원이 핵심- Hydration 실패 시 클라이언트에서 전체 페이지를 다시 렌더링해야 함
Universal JavaScript 패턴의 핵심을 알게 되었습니다.
- 같은 코드가 서버와 클라이언트에서 다르게 동작해야 함
typeof window체크로 환경 분기가 가장 기본적인 패턴- 서버에서는 파일 시스템 접근, 클라이언트에서는 브라우저 API 사용
성능 최적화 관점에서의 인사이트
SSG의 성능 이점을 확인했습니다.
- 정적 파일들이 CDN에서 캐싱되어 전 세계 어디서나 빠른 접근 가능
- 서버 부하 없이 대량의 동시 접속 처리 가능
- 초기 페이지 로드 시간이 CSR 대비 50% 이상 단축
메모리 사용량 최적화가 중요했습니다.
- 500개 이상의 상품 페이지 SSG 생성 시 메모리 사용량 급증
items.json파일을 한 번에 로드하지 않고 필요한 데이터만 선택적 로딩- 각 페이지 생성 후 메모리 해제를 위한 명시적 정리 로직 추가
학습 갈무리
Q1. 현재 구현한 SSR/SSG 아키텍처에서 확장성을 고려할 때 어떤 부분을 개선하시겠습니까?
Q2. Express 서버 대신 다른 런타임(Cloudflare Workers, Vercel Edge Functions 등)을 사용한다면 어떤 점을 수정해야 할까요?
Q3. 현재 구현에서 성능 병목이 될 수 있는 지점은 어디이고, 어떻게 개선하시겠습니까?
Q4. 1000개 이상의 상품 페이지를 SSG로 생성할 때 고려해야 할 사항은 무엇입니까?
Q5. Hydration 과정에서 사용자가 느낄 수 있는 UX 이슈는 무엇이고, 어떻게 개선할 수 있을까요?
Q6. 이번 과제에서 학습한 내용을 실제 프로덕션 환경에 적용할 때 추가로 고려해야 할 사항은?
Q7. Next.js 같은 프레임워크 대신 직접 구현한 SSR/SSG의 장단점은 무엇인가요?
Q8. Next.js 를 이용하여 SSG 방식으로 배포하려면 어떻게 해야 좋을까요?
코드 품질 향상
자랑하고 싶은 구현
개선하고 싶은 부분
리팩토링 계획
학습 연계
다음 학습 목표
아직도 이해가 잘 된 상태는 안된거 같아서 일단 이번 코드를 다시 보고 공부를 하려고 합니다.
실무 적용 계획
/vanilla는 어찌저쩌 했지만 이해되지 않은 상태에서 기본과제를 하고 심화를 하지 못해서 적을 수 있는 내용이 없네요;; 항해 끝난 다음에 다시 한번 보면서 이해해 보겠습니다.
리뷰 받고 싶은 내용
- "React SSR에서 renderToString(
)을 사용해서 HTML을 생성하는데, 실제 상품 데이터나 카테고리 정보가 HTML에 포함되지 않습니다. 구체적으로: - 서버에서 loadHomePageData()로 데이터는 로드하지만, renderToString(
)에서는 빈 HTML만 생성됩니다. - App 컴포넌트에서 useCurrentPage()가 서버에서 null을 반환해서 실제 페이지 컴포넌트가 렌더링되지 않습니다.
- window.INITIAL_DATA 체크는 서버에서 항상 false가 되어서 데이터를 사용한 렌더링이 실행되지 않습니다. 서버에서 데이터를 로드하고 HTML에 포함시키는 올바른 방법은 무엇인가요? React SSR에서 서버 데이터를 컴포넌트에 전달하는 패턴은 어떻게 구현해야 할까요?"
- 서버에서 loadHomePageData()로 데이터는 로드하지만, renderToString(
- "바닐라 JavaScript SSR에서 serverRouter.target이 null을 반환해서 PageComponent() 호출 시 에러가 발생합니다. 라우트 등록은 되어 있는데 매칭이 안 되는 문제를 어떻게 해결해야 할까요?"
- "바닐라 JavaScript SSG에서 상품 상세 페이지 생성 시 initialData.product가 undefined가 되어 페이지가 생성되지 않습니다. 동적 라우트 매칭이 제대로 작동하지 않는 문제를 어떻게 해결해야 할까요?"
- "바닐라 JavaScript SSR에서 http://localhost를 하드코딩하고 있는데, 실제 배포 환경에서는 다른 도메인을 사용할 수 있습니다. URL을 환경변수로 처리하는 것이 좋을까요?"
과제 피드백
안녕하세요 지혜님! 기본과제는 통과해주셨는데 심화과제에서 문제를 겪으셨군요 ㅠㅠ 어쨌든 포기하지 않고 잘 진행해주셔서 감사합니다!! 고생하셨어요!!
서버에서 데이터를 로드하고 HTML에 포함시키는 올바른 방법은 무엇인가요? React SSR에서 서버 데이터를 컴포넌트에 전달하는 패턴은 어떻게 구현해야 할까요?
main-server.tsx를 보시면 server router 를 만들어서 사용하고 있어요! 그런데 useCurrentPage 훅을 보시면 server router가 아니라 client router 를 사용하고 있습니다.
그래서 useCurrentPage가 server rouer 를 사용하도록 설정을 해줘야한답니다! react의 context/provider 를 이용하여 이 문제를 해결해야 하는데, 이에 대한 부분은 과제 솔루션을 한 번 참고해보시면 좋겠어요!
"바닐라 JavaScript SSR에서 serverRouter.target이 null을 반환해서 PageComponent() 호출 시 에러가 발생합니다. 라우트 등록은 되어 있는데 매칭이 안 되는 문제를 어떻게 해결해야 할까요?"
지금 이 부분은 해결된 것 같네요!
"바닐라 JavaScript SSG에서 상품 상세 페이지 생성 시 initialData.product가 undefined가 되어 페이지가 생성되지 않습니다. 동적 라우트 매칭이 제대로 작동하지 않는 문제를 어떻게 해결해야 할까요?"
지금 이 부분도 해결이 된 것 같네요! 아마 기존에 url을 넣어줘야 되는데 url을 넣어주지 않아소 발생한 문제처럼 보여요 ㅎㅎ
"바닐라 JavaScript SSR에서 http://localhost를 하드코딩하고 있는데, 실제 배포 환경에서는 다른 도메인을 사용할 수 있습니다. URL을 환경변수로 처리하는 것이 좋을까요?"
배포 환경마다 다른 주소를 사용하는 방법도 있고, SSR 시점에 도메인과 무관하게 동작하도록 만드는게 제일 좋답니다! 가령 지금 localhost가 쓰이는 부분은
const urlObj = new URL(url, "http://localhost");
이렇게 URL 객체를 만들때 쓰고 있는데, url 객체에서 도메인이 필요한게 아니라 pathname과 queryString이 필요한거라 localhost는 실제로 안 쓰이고 있어요 ㅎㅎ 그래서 중요한 부분은 아니랍니다!