과제 체크포인트
배포 링크
기본 과제 링크 : https://taeyeong0814.github.io/front_6th_chapter4-1/vanilla/ 심화 과제 링크 : https://taeyeong0814.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 (상품 상세 페이지들)
- 빌드 타임 페이지 생성
- 파일 시스템 기반 배포
구현 과정 돌아보기
가장 어려웠던 부분과 해결 과정
- 검색 기능이 제대로 동작하지 않아서 삽질했는데, 결국 URL 파라미터 파싱 로직에서 문제를 찾았습니다. 특히 Windows 환경에서의 경로 처리 때문에 더 복잡했습니다.
AS-IS (문제 상황)
// packages/vanilla/src/main-server.js (기존)
export const render = async (url, query) => {
// 단순한 문자열 처리로 URL 파싱
let pathname = url.split("?")[0];
// Windows 경로 구분자 문제
if (pathname.includes("\\")) {
pathname = pathname.replace(/\\/g, "/");
}
// base URL 제거가 불완전
if (pathname.startsWith("/front_6th_chapter4-1")) {
pathname = pathname.substring("/front_6th_chapter4-1".length);
}
return {
initialData: {
products,
categories,
totalCount,
// 검색 상태 정보 누락
},
html: HomePage({ products, totalCount }),
};
};
TO-BE (해결 후)
// packages/vanilla/src/main-server.js (수정 후)
export const render = async (url, query) => {
// 안정적인 URL 파싱
const urlObj = new URL(url, "http://localhost");
let pathname = urlObj.pathname;
// 정확한 base URL 제거
if (pathname.startsWith("/front_6th_chapter4-1/vanilla")) {
pathname = pathname.replace("/front_6th_chapter4-1/vanilla", "");
}
if (pathname.startsWith("/front_6th_chapter4-1/react")) {
pathname = pathname.replace("/front_6th_chapter4-1/react", "");
}
const initialData = {
...results,
filters: {
search: query.search || "",
category1: query.category1 || "",
category2: query.category2 || "",
sort: query.sort || "price_asc",
limit: query.limit || 20,
},
};
return {
initialData,
html: HomePage({
...results,
searchQuery: query.search || "",
filters: initialData.filters,
}),
};
};
- WebSocket server error: Port 24678 is already in use 에러가 계속 나서 포트 관리가 이렇게 어렵고 확인하기 힘든 것이구나 알게되었습니다. netstat으로 프로세스 찾아서 kill하는 과정을 수도 없이 한 것 같습니다..
구현하면서 새롭게 알게 된 개념
CSR과 SSR에 대한 차이와 개념을 더 정확하고 깊게 이해하여 적을 수 있으면 더 좋았겠다 생각했지만 아직 정확한 정보를 적기엔 부족함이 있다고 속으로 생각하여 SSR과 SSG의 차이점을 간단하게 적어 보았습니다.
AS-IS (이론적 지식)
- SSR: 서버에서 렌더링 해서 보내준다
- SSG: 빌드 타임에 미리 생성 한다
TO-BE (실제 구현 경험)
// SSR: 매 요청마다 서버에서 렌더링
app.get("*", async (req, res) => {
const rendered = await mainServer.render(req.url, req.query);
const html = template.replace("<!--app-html-->", rendered.html).replace("<!--app-head-->", rendered.head);
res.send(html);
});
// SSG: 빌드 타임에 미리 HTML 파일 생성
for (let i = 0; i < products.length; i++) {
generateStaticSite(`/product/${products[i].productId}`, {});
// → dist/vanilla/product/123/index.html 파일 생성
}
성능 최적화 관점에서의 인사이트
- 성능 최적화의 부분에서는 상품 데이터를 중복 API 호출해서 처리되지 않도록 한 부분!
학습 갈무리
해당 질문들에 대한 내용은 어려움이 있어 AI에게 질의를 통해 이해 된 내용을 작성하였습니다.
Q1. 현재 구현한 SSR/SSG 아키텍처에서 확장성을 고려할 때 어떤 부분을 개선하시겠습니까?
- 캐싱 전략: Redis 같은 캐시 서버를 도입해서 자주 조회되는 상품 데이터를 캐시하면 좋을 것 같습니다.
- CDN 연동: 정적 파일들을 CDN에 올려서 전 세계 어디서든 빠르게 로드되도록 하면 좋을 것 같습니다.
- 데이터베이스 분리: 현재는 JSON 파일을 쓰는데, 실제 DB로 바꾸고 읽기/쓰기 분리하면 성능이 더 좋아질 것 같습니다.
Q2. Express 서버 대신 다른 런타임(Cloudflare Workers, Vercel Edge Functions 등)을 사용한다면 어떤 점을 수정해야 할까요?
- Cold Start 문제: 함수가 처음 실행될 때 시간이 걸리니까, 자주 사용되는 함수는 미리 워밍업해야 할 것 같습니다.
- 메모리 제한: 서버리스는 메모리 사용량에 제한이 있어서, 대용량 데이터 처리할 때는 배치 처리가 더 중요해질 것 같습니다.
- API 차이: Express의
req,res객체 대신 각 플랫폼의 API를 써야 하니까, 추상화 레이어를 만들어야 할 것 같습니다. - Edge Computing: 전 세계 여러 지역에서 실행되니까 지연시간은 줄어들지만, 상태 관리가 더 복잡해질 것 같습니다.
Q3. 현재 구현에서 성능 병목이 될 수 있는 지점은 어디이고, 어떻게 개선하시겠습니까?
- 서버 렌더링: 상품이 많아지면
renderToString이 오래 걸릴 것 같아요. 메모이제이션이나 캐싱을 도입하면 좋을 것 같습니다. - 메모리 누수: 서버에서 계속 렌더링하다 보면 메모리가 쌓일 수 있어서, 주기적으로 가비지 컬렉션을 해야 할 것 같습니다.
- 번들 크기: 현재 모든 코드가 한 번에 로드되는데, 코드 스플리팅으로 필요한 부분만 로드하면 초기 로딩이 빨라질 것 같습니다.
- 데이터 로딩: 상품 목록을 한 번에 다 가져오는 대신, 페이지네이션이나 무한 스크롤을 도입하면 좋을 것 같습니다.
Q4. 1000개 이상의 상품 페이지를 SSG로 생성할 때 고려해야 할 사항은 무엇입니까?
- 빌드 시간: 모든 페이지를 한 번에 만들면 시간이 오래 걸리니까, 병렬 처리나 증분 빌드를 도입해야 할 것 같습니다.
- 메모리 관리: 1000개 페이지를 동시에 만들면 메모리 부족이 날 수 있어서, 배치 단위로 나눠서 처리해야 할 것 같아요.
- 캐시 전략: 상품 정보가 바뀔 때마다 모든 페이지를 다시 만들 필요는 없으니까, 변경된 부분만 재빌드하는 전략이 필요할 것 같습니다.
- CDN 무효화: 페이지가 업데이트되면 CDN 캐시도 갱신해야 하니까, 무효화 전략을 미리 세워야 할 것 같습니다.
Q5. Hydration 과정에서 사용자가 느낄 수 있는 UX 이슈는 무엇이고, 어떻게 개선할 수 있을까요?
Hydration 중에 사용자가 버튼을 클릭해도 반응이 없어서 답답할 것 같습니다.
- 인터랙션 차단: JavaScript가 로드되는 동안 버튼이 안 눌려서 사용자가 답답해할 것 같아요. 로딩 인디케이터를 보여주면 좋을 것 같습니다.
- 로딩 상태: 페이지가 로드되는 동안 스켈레톤 UI나 로딩 스피너를 보여주면 사용자가 기다리는 동안 덜 답답할 것 같습니다.
- Progressive Enhancement: JavaScript가 없어도 기본적인 기능(링크 이동 등)은 동작하도록 하면 더 좋을 것 같습니다.
- 점진적 로딩: 중요한 부분부터 먼저 보여주고, 나머지는 나중에 로드하는 방식도 좋을 것 같습니다.
Q6. 이번 과제에서 학습한 내용을 실제 프로덕션 환경에 적용할 때 추가로 고려해야 할 사항은?
실제 서비스에 적용하려면 훨씬 더 많은 걸 고려해야 할 것 같습니다.
- 모니터링: 서버가 잘 동작하는지, 에러가 발생하는지 실시간으로 모니터링해야 할 것 같아요. Sentry 같은 도구를 써야 할 것 같습니다.
- 에러 처리: 현재는 간단한 try-catch만 있는데, 실제로는 더 세밀한 에러 핸들링과 Fallback 페이지가 필요할 것 같습니다.
- 보안: XSS 공격이나 CSRF 공격을 방지하기 위해 CSP 헤더나 보안 미들웨어를 추가해야 할 것 같습니다.
- 로깅: 사용자가 어떤 페이지를 많이 보는지, 에러가 어디서 발생하는지 로그를 남겨서 분석해야 할 것 같습니다.
Q7. Next.js 같은 프레임워크 대신 직접 구현한 SSR/SSG의 장단점은 무엇인가요?
직접 구현해보니 Next.js가 얼마나 많은 걸 대신 해주는지 알게 되었습니다.
장점:
- 깊은 이해: SSR/SSG가 어떻게 동작하는지 내부 구조까지 알게 되었습니다.
- 커스터마이징: 원하는 대로 자유롭게 수정할 수 있어서 특별한 요구사항이 있을 때 유리할 것 같습니다.
- 학습 효과: 프레임워크 없이 구현해보니까 웹의 기본 원리를 더 잘 이해하게 되었습니다.
단점:
- 유지보수: 버그 수정이나 기능 추가할 때 직접 다 해야 해서 시간이 많이 걸릴 것 같습니다.
- 안정성: Next.js는 많은 사람들이 테스트해봤는데, 직접 구현한 건 예상치 못한 문제가 있을 수 있을 것 같습니다.
- 생태계: Next.js는 플러그인이나 라이브러리가 많은데, 직접 구현하면 이런 혜택을 못 받을 것 같습니다.
Q8. Next.js 를 이용하여 SSG 방식으로 배포하려면 어떻게 해야 좋을까요?
Next.js로 SSG 배포하면 훨씬 간단할 것 같습니다.
- getStaticProps: 페이지별로 필요한 데이터를 미리 가져오는 함수를 만들면 됩니다.
- getStaticPaths: 동적 라우트(상품 상세 페이지 등)에서 어떤 경로들을 미리 생성할지 정의하면 됩니다.
- 빌드 최적화: Next.js가 자동으로 코드 스플리팅이나 이미지 최적화를 해주니까 성능도 좋아질 것 같습니다.
- 배포: Vercel에 배포하면 자동으로 SSG로 빌드되고 CDN에 올라가니까 관리가 편할 것 같습니다.
- 증분 정적 재생성: 데이터가 바뀔 때마다 자동으로 페이지를 다시 생성해주는 기능도 있어서 좋을 것 같습니다.
코드 품질 향상
자랑하고 싶은 구현
어떤 부분이 자랑 할 수 있는 코드일까.. 동작을 위한 과제였기 때문에 회고 할 수 있는 부분이 없습니다. 음.. main-server.js 에서 라이브러리 없이 Node 내장 모듈 new URL 사용해서 간단하게 처리한 부분..?
// ===== 메인 렌더 함수 =====
export const render = async (url, query) => {
try {
// URL에서 경로 부분만 추출 (쿼리 파라미터 제외)
const urlObj = new URL(url, "http://localhost");
let pathname = urlObj.pathname;
// base URL 제거 (예: /front_6th_chapter4-1/vanilla/ -> /)
if (pathname.startsWith("/front_6th_chapter4-1/vanilla")) {
pathname = pathname.replace("/front_6th_chapter4-1/vanilla", "");
}
if (pathname.startsWith("/front_6th_chapter4-1/react")) {
pathname = pathname.replace("/front_6th_chapter4-1/react", "");
}
// 빈 경로는 루트로 처리
if (pathname === "" || pathname === "/") {
pathname = "/";
}
개선하고 싶은 부분
핵심적인 부분에 대한 구현은 파악하고 100% 이해하며 진행한 부분보다는 AI와 항해인원들의 도움을 받아 개선 할 부분을 아직 모르겠습니다. 하지만 에러 처리 부분은 단순하게 보여지는 부분 말고 화면 단위로 깔끔하게 바꿀 수 있을 것 같다는 생각을 했습니다.
console.error("❌ SSR 에러:", error);
return {
head: "<title>에러</title>",
html: "<div>서버 오류가 발생했습니다.</div>",
initialData: { error: error.message },
};
리팩토링 계획
준일 코치님의 추가 된 솔루션 코드를 베이스로 라우터와 스토어 부분을 더 나누어서 구조화 시킬 수 있다고 하셨던 부분을 리팩토링 해보고자 합니다.
학습 연계
다음 학습 목표
더 배우고 싶어진 주제라기 보단 CSR, SSR, SSG, TTV, TTI ... 에 개념을 면접 질문식으로 대답 할 수 있고 다른 사람에게 설명 할 수 있는 학습을 해보고자 합니다.
실무 적용 계획
실무에 솔루션 프로젝트와 정부과제 프로젝트는 어떻게 구성이 되어 있으려나... 적용보다는 내가 일하고 있는 프로젝트들은 어떻게 구성이 되어있는지 파악해 보는 것부터 해보겠습니다 ㅎㅎ
리뷰 받고 싶은 내용
테오의 멘토링 때 해당 과제에 대한 내용을 면접화하여 어떻게 내 것으로 만들어야 할 지 깨닫는 시간이 있었습니다. CSR, SSR, SSG가 어떤 개념인지 설명을 해보세요. 를 시작으로 우리가 React를 쓰는 이유. 왜 쓰는지 SSR를 SEO의 최적화 때문에만 쓰는건지 다른 이유는 없는지 그럼 react 말고 next 쓰지 등....
해당 내용들을 깔끔하게? 작성 할 수 있는 인사이트가 있다면 추천 해주시고
이 부분(해당 과제)에 있어서 코드 단위의 개념으로는 이번 과제가 너무 어려웠다면 한 단계 아래? 혹은 더 기본을 파악 할 수 있도록 직접 실무 해보는 방법은 어떤 방법이 있을까요?
과제 피드백
수고했습니다. 이번 과제는 SSR과 SSG를 직접 구현해보면서 렌더링 전략의 차이점과 성능 최적화 방법을 체험하는데 목적이 있었습니다.
AS-IS/TO-BE 형태로 이론과 실제 구현 경험을 비교한 부분이 인상적이네요. 항해의 커리큘럼에서도 이론이 아니라 구현을 통해서 실습을 통해서 학습하기를 유도하는 것도 이 때문이에요. 인간의 언어는 그 자체가 추상적이기 때문에 이론또한 추상적일 수 밖에 없는데 실제 컴퓨터 세상은 그렇지 않으니까요.
Q) 테오의 멘토링 때 해당 과제에 대한 내용을 면접화하여 어떻게 내 것으로 만들어야 할 지 깨닫는 시간이 있었습니다. CSR, SSR, SSG가 어떤 개념인지 설명을 해보세요. 를 시작으로 우리가 React를 쓰는 이유. 왜 쓰는지 SSR를 SEO의 최적화 때문에만 쓰는건지 다른 이유는 없는지 그럼 react 말고 next 쓰지 등.... 해당 내용들을 깔끔하게? 작성 할 수 있는 인사이트가 있다면 추천 해주시고
=> 우선적으로는 필요한 개념과 키워드를 최대한 다 모아보세요. AI를 통해도 좋고 공식문서를 통해서도 좋습니다. 최대한 다양한 개념과 이론 키워드 배경 사용법 특징, 대체가능한 기술, 왜 이 기술이 주류가 되었는지 등 다양한 정보들을 수집해봅니다. 요새는 AI가 있으니까 뭘 알아야 하는지도 물어보면 알려주잖아요.
=> 인간의 기억의 특징은 단순정보를 기억하는게 아니라 크게 3가지로 하나는 공간(위치)적으로 하나는 인과(시간순)으로 그리고 나머지는 패턴(예측)이라는 측면으로 발달했다고 해요. 그래서 내가 학습했던 개념들을 순서대로 재배치하고 인과에 맞게 순서대로 이야기를 만들어보는 훈련을 하면 좋습니다. 왜냐하면? 그러니까? 그 이유는? 또 다른 방법으로는? 이와 유사하게? 하지만 이 방법으로는? 등등 적절한 접속사들을 동원해서 그럴싸한 인과를 가진 이야기를 만들어 내는 거죠.
=> AI에게 이걸 시켜보면 상당히 어려워합니다. 그리고 사람은 대번에 알아요. 인과나 논리의 형식에 맞지 않는 거에서 의문을 가지거든요. 이러한 이야기들을 스스로 완성해보려고 하고 말을 해보려고 하고 글을 써보려고 하면 상당히 많이 늘 수 있습니다. 부족한 인과의 조각들을 찾기 위해서 또 공부를 하게 될거에요.
Q) 이 부분(해당 과제)에 있어서 코드 단위의 개념으로는 이번 과제가 너무 어려웠다면 한 단계 아래? 혹은 더 기본을 파악 할 수 있도록 직접 실무 해보는 방법은 어떤 방법이 있을까요?
=> 각 개념들을 이해할 수 있는 최소한의 코드를 직접 만들어 보기를 권합니다. 지금 보다 더 적은 형태로 최소한의 형태를 갖추는 거죠. 완전히 내것으로 만드는 데에는 그만한게 없어요. 그렇지만 현업에서 쓰이는 그 정도의 기술을 나 혼자서 만들기란 어렵죠. 그러니 최소한의 전제로 한번 만들어보기를 바랍니다. 가령 SSR의 핵심 원리는 renderToString, initData, hydrate render 이 3가지 잖아요? 그걸 100% 다 구현하지는 않더라도 스스로 구성정도만 해보면서 감을 잡아보는 것이죠 :) 항해의 커리큘럼의 형식이 대부분 그렇잖아요ㅎ AI를 통해서 무슨 실습 과제를 해보면 좋을지 받고 해보는거에요!
=> 무엇보다 공부를 위한 실습보다는 실무에 필요한 것들을 익히기 위한 실습이면 더 좋습니다. 사실 그냥 하라고 하면 동기부여나 의미가 전혀 생기질 않으니까요ㅎ
다음 과제도 화이팅입니다!