과제 체크포인트
배포 링크
https://ldhldh07.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 (상품 상세 페이지들)
- 빌드 타임 페이지 생성
- 파일 시스템 기반 배포
구현 과정 돌아보기
가장 어려웠던 부분과 해결 과정
하이드레이션 과정에서 겪은 어려움
환경별로 데이터를 바꿔치는 조건을 분기하는 과정이 좀 머리아팠습니다.
고치려고 개선하면 Unexpected token 오류와 무한 로딩, 하이드레이션이 안되는 오류 중 반드시 하나는 났습니다.
침착하게 하나하나 로직 파가면서 고민했으면 해결할 수 있었을까 싶기도 했습니다. 주로 머리가 안돌아가는 시점에 과제를 했다는 이슈로 그러지 못한 것이 아쉬웠습니다.
const enableMocking = () =>
import("./mocks/browser.js").then(({ worker }) =>
worker.start({
serviceWorker: {
url: `${BASE_URL}mockServiceWorker.js`,
},
onUnhandledRequest: "bypass",
}),
);
function hydrateFromServer() {
if (typeof window !== "undefined" && window.__INITIAL_DATA__) {
const data = window.__INITIAL_DATA__;
productStore.dispatch({ type: PRODUCT_ACTIONS.SETUP, payload: data });
delete window.__INITIAL_DATA__;
}
}
function main() {
hydrateFromServer();
registerAllEvents();
registerGlobalEvents();
loadCartFromStorage();
initRender();
router.start();
}
if (import.meta.env.DEV || (typeof window !== "undefined" && window.location.hostname === "localhost")) {
enableMocking().then(main);
} else {
main();
}
// AI에게 의탁해서 뭔가 이것저것 생겼지만 제대로 동작하지 못한다
구현하면서 새롭게 알게 된 개념
막연하게 아는 ssr이 어떻게 동작하는지 파악했습니다.
학습 및 멘토링 과정에서 ssr이라는 시스템이 어떤 맥락에서 탄생했는지 알 수 있었습니다. 이번 과제는 코드나 동작적인 것을 이해했다는 점보다는 이런 ssr을 왜 쓰는지 알게 된 점이 보다 더 인상깊었습니다.
기존의 근본의 웹 렌더링 방식부터 리액트와 같은 SPA가 생기고 SSR을 통해 다시 근본으로 돌아가는 그 흐름을 이해했습니다. 이 흐름을 이해하면서 SSR의 동작 방식을 더 잘 이해할 수 있었습니다.
성능 최적화 관점에서의 인사이트
성능 측면은 아니지만 SSR환경에서의 라우터/스토어의 동시성 문제는 고민해봤던 이슈입니다. 팀원들과도 관련해서 이야기를 나눠봤습니다.
처음에 든 생각은 스토어는 당연히 요청에 따라 상태가 오염되면 안되지 않나 싶었습니다. 하지만 라우터는 애초에 오염될 상황이 없지 않나 싶어서 그냥 별도의 처리를 안했습니다.
const routes = [];
export const route = {
add: (pattern, handler) => {
...
},
find: (url) => {
...
},
};
실제로 테스트를 해봤을 때 동시에 여러개를 실행하면 오류가 나는데 하나만 돌리면 통과하는 흥미로운 상황이 있었습니다. 동시성 오류에 대해 직접 체험을 해본 유효한 경험이었습니다.
학습 갈무리
Q1. 현재 구현한 SSR/SSG 아키텍처에서 확장성을 고려할 때 어떤 부분을 개선하시겠습니까?
CDN 캐싱을 이용한 SSG/ISR 페이지 구성을 통해 개선하겠습니다.
Q2. Express 서버 대신 다른 런타임(Cloudflare Workers, Vercel Edge Functions 등)을 사용한다면 어떤 점을 수정해야 할까요?
Express에 의존하는 api를 제거합니다. 대신 각 환경별로 대응할 수 있는 미들웨어 단계의 계층을 거쳐서 호환될 수 있도록 수정해야 합니다.
Q3. 현재 구현에서 성능 병목이 될 수 있는 지점은 어디이고, 어떻게 개선하시겠습니까?
라우터/스토어의 동시성 처리를 하지 못했습니다. 요청별로 독립된 컨텍스트 내에서 동작하도록 개선해야 합니다.
Q4. 1000개 이상의 상품 페이지를 SSG로 생성할 때 고려해야 할 사항은 무엇입니까?
공통적인 부분은 캐싱하고 다른 부분만 새롭게 생성하는 패턴을 고려해보겠습니다.
Q5. Hydration 과정에서 사용자가 느낄 수 있는 UX 이슈는 무엇이고, 어떻게 개선할 수 있을까요?
하이드레이션이 이뤄지는 찰나의 순간 동작을 하지 않습니다. js 요소를 쪼개서 실제 ux 중요도가 높은 js 동작을 먼저 로딩합니다.
Q6. 이번 과제에서 학습한 내용을 실제 프로덕션 환경에 적용할 때 추가로 고려해야 할 사항은?
에러 로깅 및 에러 처리 로직을 좀더 고도화하는 것을 우선하겠습니다.
Q7. Next.js 같은 프레임워크 대신 직접 구현한 SSR/SSG의 장단점은 무엇인가요?
장점: 가장 크게 느낀건 에러 로깅을 원하는 형식으로 할 수 있습니다. 단점: 최적화를 하기 위해서는 코드의 복잡도가 높아지고 팀 단위로 작업할때는 아키텍쳐 공유가 되지 않습니다.
Q8. Next.js 를 이용하여 SSG 방식으로 배포하려면 어떻게 해야 좋을까요?
Vercel과 의존성이 있는 프레임워크인만큼 Vercel에서 제공해주는 틀 안에서 배포하는 것이 좋을 듯 합니다. 캐싱을 통해 정적 파일 생성을 최소화하고 그 과정에서 CDN을 활용합니다.
실무 적용 계획
개발중인 사이트에 최적화된 ssr 구조 적용할 계획입니다. SEO 관련된 작업 또한 진행하고자 합니다.
과제 피드백
수고했습니다. 이번 과제는 SSR과 SSG를 직접 구현해보면서 렌더링 전략의 차이점과 성능 최적화 방법을 체험하는데 목적이 있었습니다.
"하이드레이션 과정에서 겪은 어려움 ... 고치려고 개선하면 Unexpected token 오류와 무한 로딩, 하이드레이션이 안되는 오류 중 반드시 하나는 났습니다." ㅋㅋ 회고에서 고생을 했다는게 느껴지네요. 그래도 고생을 한만큼 "... 막연하게 아는 ssr이 어떻게 동작하는지 파악했습니다." 가 되었다니 참 좋습니다
"실제로 테스트를 해봤을 때 동시에 여러개를 실행하면 오류가 나는데 하나만 돌리면 통과하는 흥미로운 상황이 있었습니다. 동시성 오류에 대해 직접 체험을 해본 유효한 경험이었습니다." 아주 좋습니다. 맞아요. 괜히 브라우저 언어가 싱글 스레드로 만든게 아니죠. 브라우저 밖으로 가게 되면 당연하다 싶은 것들이 아닌 순간들을 겪게 되는데 SSR을 다루면서 조금 더 확장된 세상을 많이 경험하게 되는 계기가 되었기를 바래요.
마지막까지 화이팅입니다!