Skip to content

[6팀 이태영] Chapter 4-1 성능 최적화#12

Open
taeyeong0814 wants to merge 17 commits intohanghae-plus:mainfrom
taeyeong0814:main
Open

[6팀 이태영] Chapter 4-1 성능 최적화#12
taeyeong0814 wants to merge 17 commits intohanghae-plus:mainfrom
taeyeong0814:main

Conversation

@taeyeong0814
Copy link

@taeyeong0814 taeyeong0814 commented Sep 1, 2025

과제 체크포인트

배포 링크

기본 과제 링크 : 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 쓰지 등....

해당 내용들을 깔끔하게? 작성 할 수 있는 인사이트가 있다면 추천 해주시고

이 부분(해당 과제)에 있어서 코드 단위의 개념으로는 이번 과제가 너무 어려웠다면 한 단계 아래? 혹은 더 기본을 파악 할 수 있도록 직접 실무 해보는 방법은 어떤 방법이 있을까요?

이태영 and others added 17 commits September 1, 2025 21:05
- 초기 렌더링 및 라우팅
- 검색 및 상품/카테고리 mock 데이터 처리
- 동적 메타태그 생성
- 서버 상태 초기화 및 Hydration 지원
- 프로덕션 환경에서 HTML 템플릿 직접 사용
- 홈페이지 및 상품 상세 페이지에 대한 라우팅 로직 구현
- 404 페이지 처리 로직 추가
- Vite 서버를 사용하여 정적 사이트 생성 로직 개선
- 상품 상세 페이지 및 404 페이지에 대한 동적 HTML 생성 추가
- 라우팅 로직 및 에러 처리 개선
- 모듈 경로 수정으로 코드 정리
- 상품 데이터 가져오기 방식 변경 및 코드 정리
- 404 페이지 처리 로직 개선
- HTML 템플릿 경로 수정 및 동적 HTML 생성 로직 강화
- HomePage 컴포넌트에 검색 쿼리 및 필터를 props로 전달하여 동적 렌더링 지원
- SearchBar 컴포넌트에 필터링 옵션 추가 및 기본값 설정
- pnpm-lock.yaml 파일에서 의존성 버전 및 형식 통일
- react 및 vanilla 패키지의 package.json에서 스크립트 및 의존성 추가/수정
- cross-env 및 tsx 추가로 환경 설정 및 빌드 프로세스 개선
- React SSR 및 Hydration 관련 체크리스트 및 구현 가이드 문서화
- 동적 라우트 SSG 및 서버 데이터 로딩 방법 설명 추가
- Universal Router 및 클라이언트 상태 복원 방법 안내
- Router.ts 파일을 MemoryRouter와 BaseRouter로 분리하여 구조화
- createStorage.ts에서 SSR 안전한 스토리지 기본값 추가
- useRouter, useStorage, useStore 훅에서 서버 스냅샷 지원 추가
- tsconfig.json 파일에서 형식 통일 및 정리
- pnpm-lock.yaml 및 package.json에서 의존성 버전 업데이트
- URL 패턴에 따라 데이터 로드 방식 변경
- 검색 쿼리 및 카테고리 필터링 기능 추가
- 정렬 및 개수 제한 기능 구현
- 상품 상세 페이지 로딩 로직 개선
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant