Chapter 4-1. 성능최적화: SSR, SSG, Infra
사용자 요청 → 서버가 빈 HTML 전송 → 브라우저에서 JS 실행 → 데이터 페칭 → 화면 렌더링
특징:
(SSG까지 구현했지만, SSR을 현재 방식으로 작성)
사용자 요청 → 서버에서 데이터 페칭 → 서버에서 HTML 생성 → 완성된 HTML 전송 → 브라우저에서 Hydration
특징:
window 객체 없음window 객체 있음window.__INITIAL_DATA__로 전달1. 사용자가 URL 요청
↓
2. Express 서버가 요청 수신 (server.js)
↓
3. URL에서 쿼리 파라미터 파싱 (server.js)
↓
4. Vite SSR 모드로 HTML 템플릿 로드 (개발) 또는 빌드된 파일 로드 (프로덕션)
↓
5. main-server.js의 render 함수 호출 (url, query 전달)
↓
6. 라우트 등록 (/, /product/:id/, .*)
↓
7. 쿼리 파라미터를 router.query에 먼저 설정
↓
8. URL에서 pathname만 추출 (쿼리 파라미터 제거, base path 제거)
↓
9. ServerRouter.push(pathname)로 라우팅 처리
↓
10. 페이지 컴포넌트의 getServerSideProps 실행 (서버에서 데이터 페칭)
- router.query를 사용하여 쿼리 파라미터 기반 데이터 가져오기
↓
11. Store에 초기 데이터 주입
↓
12. 페이지 컴포넌트 렌더링 (HTML 문자열 생성)
↓
13. window.__INITIAL_DATA__ 데이터 형식 변환 (HomePage의 경우)
- pagination 제거, totalCount로 변환
↓
14. HTML 템플릿에 렌더링 결과 삽입
- head 태그에 title 삽입
- window.__INITIAL_DATA__ 스크립트 태그 추가
↓
15. 완성된 HTML을 클라이언트에 전송
↓
16. 브라우저에서 HTML 수신 및 표시 (이미 데이터가 있음!)
↓
17. main.js 실행 (클라이언트 Hydration)
↓
18. window.__INITIAL_DATA__에서 데이터 읽어서 Store 초기화
↓
19. 클라이언트 라우터로 전환, 인터랙티브 기능 활성화
main-server.js 쪽이다.export const render = async (url, query) => {
// 1. 라우트 등록
router.addRoute("/", HomePage);
router.addRoute("/product/:id/", ProductDetailPage);
router.addRoute(".*", NotFoundPage);
// 2. 쿼리 파라미터를 먼저 설정 (getServerSideProps에서 사용)
router.query = query;
// 3. URL에서 pathname만 추출
const urlObj = new URL(url, "http://localhost:3000");
let pathname = urlObj.pathname;
// base path 제거
if (BASE_URL !== "/" && pathname.startsWith(BASE_URL)) {
pathname = pathname.slice(BASE_URL.length);
if (!pathname || pathname === "") {
pathname = "/";
}
}
// 4. 라우터로 경로 매칭
router.push(pathname);
// 5. 매칭된 페이지 컴포넌트 가져오기
const PageComponent = router.target;
// 6. 서버에서 데이터 페칭 (getServerSideProps)
// 이 시점에 router.query가 이미 설정되어 있음
const initData = await PageComponent.getServerSideProps?.();
// 7. Store에 초기 데이터 주입
if (PageComponent === HomePage) {
productStore.dispatch({
type: PRODUCT_ACTIONS.SETUP,
payload: {
products: initData.products,
categories: initData.categories,
totalCount: initData.pagination.total,
loading: false,
status: "done",
},
});
}
// 8. 동적 title 생성
let pageTitle = "쇼핑몰 - 홈";
if (PageComponent === ProductDetailPage && initData?.product) {
pageTitle = `${initData.product.title} - 쇼핑몰`;
} else if (PageComponent === NotFoundPage) {
pageTitle = "404 - 쇼핑몰";
}
// 9. E2E 테스트 형식에 맞게 데이터 변환
let dataForClient = initData;
if (PageComponent === HomePage && initData) {
dataForClient = {
products: initData.products,
categories: initData.categories,
totalCount: initData.pagination?.total ?? 0,
};
}
// 10. 페이지 컴포넌트 렌더링 (HTML 문자열 반환)
return {
head: `<title>${pageTitle}</title>`,
html: PageComponent(), // HTML 문자열
data: dataForClient, // 클라이언트로 전달할 데이터
};
};
export const router =
typeof window !== "undefined"
? new Router(BASE_URL) // 클라이언트: 브라우저 라우터
: new ServerRouter(BASE_URL); // 서버: 서버 라우터

슈ㅜ루룩!