j2h30728 님의 상세페이지[6팀 이지현] Chapter 4-1 성능 최적화 🧦

과제 체크포인트

배포 링크

기본과제 (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 (상품 상세 페이지들)
  • 빌드 타임 페이지 생성
  • 파일 시스템 기반 배포

구현 과정 돌아보기

가장 어려웠던 부분과 해결 과정

  • 처음 부터 힘들었던 것 같습니다 ㅠ.ㅠ
  • 사실 심화과제는....시간이 없어서 커서와 함께 했습니다. 기본과제와 같은 방식으로 해결하려 했습니다.
  • 하지만, 시간이 촉박하기도 하면서 제 마음대로 잘 되지않더라구요. 원하던 구현들도 사실 전부 엎어버렸었어요. 어쩌면 손수 제가 짰더라면 더 빨리 끝났었겠다라고 회고 할정도로요. 그래서 결국에는 과제 통과를 위한 코드를 생성하고 말았습니다. (그래도 통과는 해야지, 한잔 해)
기본과제

제가 구현한 내용은 아래와 같습니다.

  1. 서버사이드에서 msw를 실행시켜 ssr에 사용하고 빌드한 값으로 ssg에 사용합니다.
  2. 렌더 후에는 서버라우터에서 클라이언트라우터를 교체하여 csr로 사용한다.
심화과제

과제의 csr 기본 코드를 그대로 둔채, 서버사이드에서 실행하는 코드에서 직접 서버컴포넌트를 생성하여 하이드레이션 하는 작업으로 작성했습니다. 그렇기 때문에 DRY 원칙을 지키지 못한채 서버컴포넌트와 클라이언트 컴포넌트 두 벌이 생기게 되었고 유지보수 관리포인트가 늘어나게 됐습니다. 더욱이 리액트의 기능들을 쓰지않은것도 있었죠

// packages/react/src/main-server.tsx

const ServerProductDetailPage: React.FC<{ data: ProductDetailData }> = ({ data }) => {
  // 클라이언트 컴포넌트와 거의 동일한 구조를 중복 구현
  return (/* JSX */);
};

심화과제에서는 Universal 컴포넌트 패턴을 제대로 적용하지 못해 같은 기능의 컴포넌트를 두 번 작성하게 되었습니다. 손수 제가 짰더라면 더 효율적으로 할 수 있었을 텐데, 시간에 쫓겨 과제 통과만을 목표로 하다 보니 이런 결과가 나왔습니다.

서버컴포넌트를 직접 만들다보니, 실제 리액트에서 서버사이드에서 지원하는 기능을 쓰지않은 채 과제를 진행했습니다. 그래서 테스트를 통과할수는 있지만, 과제의 의도와는 멀리떨어져 버린 결과물은 만들어버렸습니다. ㅠㅠ

나중에 다시 직접 해보려구요!

하이드레이션 과정에서의 상태 동기화

서버에서 렌더링된 상태와 클라이언트에서 복원되는 상태를 일치시키는 것이 생각보다 복잡했습니다. 특히 window.__INITIAL_DATA__를 통한 상태 전달과 복원 과정에서 타이밍 이슈가 있었습니다. ㅠㅠ

// packages/vanilla/src/main.js

async function restoreSSRState() {
  if (typeof window !== "undefined" && window.__INITIAL_DATA__) {
    const initialState = window.__INITIAL_DATA__;
    const { productStore } = await import("./stores/index.js");
    if (productStore && initialState) {
      productStore.setState(initialState);
    }
    delete window.__INITIAL_DATA__;
    return true;
  }
  return false;
}

서버에서 주입한 초기 데이터를 클라이언트에서 정확히 복원하고, 메모리 누수 방지를 위해 사용 후 INITIAL_DATA 값을 즉시 삭제하도록 구현했습니다.

구현하면서 새롭게 알게 된 개념

  • 유니버설 렌더링에 대해선 이론은 알고있었지만 실제로 구현해보며 알게된 것이 새로웠어요.
  • 사실 취준하면서 next.js의 page router, app router를 공부했어서 대충 원리는 알고있었어요. 그런데 구현하려고하니 막막하더라구요. 그냥 겁이 많이 났어요.

msw

msw 설정 세팅을 client 에서만 해보고 서버에서는 해본적이 없었어요. 사실 테스트 코드 주차에서 msw 가 서버에서 돌아가기는 하나, 오프코치님이 이미 설정을 해주시고 과제를 내주신거라서 딱히 생각을 못했던 것 같아요.

ssr필요한 데이터를 프리페칭하기 위해서 서버사이드에서 msw을 돌리고 싶었는데 잘 몰라서 다른 수강생에게 도움 받았었습니다. 이게 또 다른 오픈소스 레포지토리 돌려도 예제 코드들은 실제 하드코딩한 목데이터를 만들어서 넣어주더라구요. 그래서 어려웠습니다.

msw의 onUnhandledRequest: "bypass", 으로 실제 네트워크요청이 공존할 수있게 처리했습니다.

// packages/vanilla/server.js
const { mswServer } = await import("./src/mocks/server.js");
mswServer.listen({
  onUnhandledRequest: "bypass",
});

SSR과 SSG의 실질적 차이점

이론적으로만 알고 있던 개념을 직접 구현하면서 체감할 수 있었습니다.

SSR: packages/vanilla/server.js에서 요청 시마다 render 함수를 호출해 HTML 생성 SSG: static-site-generate.js에서 빌드 타임에 미리 정적 HTML 파일들을 생성

유니버셜 렌더링

기본과제를 해보면서 유니버설 렌더링에 대해서는 이론은 알고 있었지만 실제로 구현 해보는것이 재밌었어요. 같은 코드를 서버와 클라이언트에서 다르게 동작해야 하는것을 고려하는 점이 제일 컸던 것 같습니다.

성능 최적화 관점에서의 인사이트

  • 과제를 하면서 성능 최적화면을 고려하진 못했습니다. 이해, 기능구현, 테스트 통과에 매몰 되어있었어요. 아쉽네요.

학습 갈무리

Q1. 현재 구현한 SSR/SSG 아키텍처에서 확장성을 고려할 때 어떤 부분을 개선하시겠습니까?

현재 구조에서 가장 시급한 개선점은 심화과제에서 서버컴포넌트를 제거하고 유니버션 렌더링을 적용하는 것입니다. 서버/클라이언트 컴포넌트 중복을 해결하고 다른 페이지가 추가되어도 손쉽게 관리 할수 있는 것이 제일 최우선 순위로 필요해 보입니다.

Q2. Express 서버 대신 다른 런타임(Cloudflare Workers, Vercel Edge Functions 등)을 사용한다면 어떤 점을 수정해야 할까요?

다른 런타임을 사용하게 된다면 서버리스 환경을 고려해야합니다. msw세팅을 변경할 뿐아니라, 서버리스 특징인 cold start를 고려해서 번들을 줄이거나 기능단위로 분산하여 배포해야함 필수적인 것으로 보입니다.

Q3. 현재 구현에서 성능 병목이 될 수 있는 지점은 어디이고, 어떻게 개선하시겠습니까?

서버 렌더링 시에 CPU 사용량을 줄여야하지않을까 싶습니다. 현재 리스트페이지는 첫페이지만, 상세페이지는 특정 아이디값만 서버 렌더링을 하긴합니다. 하지만, 컴포넌트 단위로는 코드스플리팅이 제대로 되어있지 않는 점에서 추가적으로 번들 최적화가 필요해보입니다.

또한 현재 데이터 페칭을 따로 캐싱해두고 있지않아서 불필요한 API호출을 줄이는 방법도 추가할 것. ㅏㅌ스빈다.

Q4. 1000개 이상의 상품 페이지를 SSG로 생성할 때 고려해야 할 사항은 무엇입니까?

1000개 이상이라면 증분 빌드를 사용할 것 같습니다. 단순히 빌드할 때 모든것을 html을 만들게 되면 번들 파일이 무지막지하게 늘어날 수있다는 점과 그것을 진행하는 소요시간이 부담스러울 것 같습니다.

next.js에서는 해당 기능을 단순히 옵션만 지정하면 해당 시간마다 다시 데이터페칭을 일으키는 것으로 알고있습니다. ssg처럼 빌드할 때 html을 만들지만 트리거닝 하면 추가적으로 데이터를 최신화할수 있는 기술을 선택할 것같습니다.

Q5. Hydration 과정에서 사용자가 느낄 수 있는 UX 이슈는 무엇이고, 어떻게 개선할 수 있을까요?

Q6. 이번 과제에서 학습한 내용을 실제 프로덕션 환경에 적용할 때 추가로 고려해야 할 사항은?

Q7. Next.js 같은 프레임워크 대신 직접 구현한 SSR/SSG의 장단점은 무엇인가요?

장점

  • 원리를 이해할 수 있다는 학습이 제일 클 것 같습니다.
  • 또한, 이슈가 무척 많지만 해결에는 오래걸릴 수 밖에 없는 오픈소스라이브러리 와 달리, 커스터마이징이 자유롭고 원한다면 빠르게 수정할 다는 점

단점

  • 설계에 따라 복잡해질 수 있다.
  • 유지보수 비용이 무척 비싸고 이것이 정말 안전하고 안정성이 있는 가에 대한 검증이 부족하다.
  • Next.js는 생태계가 무척 크기 때문에 다양한 관점과 방법 등을 공유하고 받을 수 있지만, 직접 만들게 되면 그럴 수가없다.

Q8. Next.js 를 이용하여 SSG 방식으로 배포하려면 어떻게 해야 좋을까요?

코드 품질 향상

자랑하고 싶은 구현

  • 없는딩...

개선하고 싶은 부분

  • 위에서 말한 심화과제에서의 유니버셜 렌더링

리팩토링 계획

  • 심화과제를 기본과제처럼 동일하게 컴포넌트 파일을 사용하되, 유니버셜 렌더링을 차용하는 것으로. 리팩토링 계획에 있습니다.

학습 연계

다음 학습 목표

실무 적용 계획

  • 회사에서 next.js를 쓰고있는데 렌더링 방식을 어떻게 사용하고 있는지 확인하여 최적화를 하지않을까 생각입니다.

리뷰 받고 싶은 내용

과제 피드백

수고했습니다. 이번 과제는 SSR과 SSG를 직접 구현해보면서 렌더링 전략의 차이점과 성능 최적화 방법을 체험하는데 목적이 있었습니다.

"손수 제가 짰더라면 더 효율적으로 할 수 있었을 텐데, 시간에 쫓겨 과제 통과만을 목표로 하다 보니 이런 결과가 나왔습니다." 라고는 했지만 개발자에게 제일 중요한 덕목은 마감이니 잘 했습니다. 무엇이 아쉬운지 부족한지를 알고 있는채로 한거라 다음 번에는 더 잘할 수 있을거에요.

"나중에 다시 직접 해보려구요!"라고 하신 의지가 좋습니다. 이번에 부족했던 부분들을 인지하고 계시니까 다음에 도전할 때는 더 체계적으로 접근하실 수 있을 거예요. 시간 압박이 있더라도 핵심 개념은 체험해보셨으니 의미 있는 학습이었다고 생각합니다. 서버에서의 MSW도 유니버셜 렌더링도 기억에 담아 두었다가 또 해보기로 해요!

마지막까지 화이팅입니다! :)