Server Side Rendering 정리

doy·2025년 8월 29일

멘토링 시간에 NextJs 서버 컴포넌트를 활용해보고 싶은데 상세페이지에서 사용하는게 옳은 방향인지 모르겠다는 밑도 끝도없는 고민을 털어놓았는데 멘토님이 수업 이후 이런 디엠을 보내주셔서 더 자세히 고민하게 되는 계기가 되었다.

정리해볼 이야기

  • 서버 컴포넌트의 개념
  • 클라이언트 컴포넌트의 개념
  • 서버 컴포넌트와 클라이언트를 나누는 기준 (어떻게 코드를 짜야 서버 컴포넌트가 되는가?)
  • 모든 컴포넌트를 서버컴포넌트로 만들었을때 무슨 문제가 생길까?
  • 서버 컴포넌트는 왜 필요할까?
  • SSR과 차이점?

서버 컴포넌트 & 클라이언트 컴포넌트 개념

서버 컴포넌트

  • React 18부터 도입됨. 서버 컴포넌트는 서버에서 실행되는 React 컴포넌트라고 볼 수 있다.
  • NextJS에서는 기본적으로 컴포넌트를 만들 때 서버 컴포넌트로 동작된다.

클라이언트 컴포넌트

  • 이벤트리스너, 상태 등 상호작용 하여 사용자에게 피드백, UI 업데이트
  • 사용하려면 ‘use client’ 추가

서버 컴포넌트와 클라이언트 컴포넌트를 나누는 기준

1

서버 컴포넌트

  • 데이터 fetch 할 경우
  • 백엔드 데이터에 직접 접근 → SEO
  • 민감한 정보 access token, api key 등 보관
  • 서버에 대한 큰 의존성 유지 / 클라이언트 측 JavaScript 감소

클라이언트 컴포넌트

  • 상호작용 및 이벤트 리스너(onClick(), onChange()) 사용
  • React Hooks. 상태 및 수명주기 관련(useState(), useReducer(), useEffect(), Tanstack Query) 등
  • 브라우저 전용 API 사용 (window, document, localStorage)
  • 클라이언트 사이드(UI) 상태관리 → zustand

모든 컴포넌트를 서버 컴포넌트로 만들면?

  • 위 클라이언트 컴포넌트에서 사용할 수 있는 기능들을 사용할 수 없음

필요성

  • 초기 페이지 로드 속도 증가, 클라이언트 측 자바스크립트 번들 크기 감소

❓SSR 그리고 서버 컴포넌트 차이점

SSR은 서버에서 렌더링 된 HTML을 가져오지만,

서버 컴포넌트는 HTML이 아닌 렌더링 할 트리 객체를 가져온다는 점이다.

트리 객체는 서버 컴포넌트, 클라이언트 컴포넌트로 구성되어 있으며 트리를 보고 DOM을 업데이트한다고 한다.

서버 컴포넌트가 SSR을 대체할 수 있는건 아니다. SSR과 서버 컴포넌트가 함께 사용된다면, 초기에 서버에서 빠르게 렌더링한 다음, SSR을 통해 이를 HTML로 렌더링하여 웹 애플리케이션 초기 로딩을 빠르게 만들 수 있다. 따라서 RSC 반드시 둘 중 하나를 선택할 필요도 없고 필요에 따라 RSC와 SSR을 적절히 함께 사용하는것이 더욱 효과적일 것이다.

SSR의 단점

1. Blinking issue

정적 사이트에서 발생했던 페이지 전환시 서버에서 즉각적으로 페이지 생성하면서 생기는 화면 깜빡임 문제가 여전히 존재한다.

2. 서버 과부하

서버에서 매 번 동적으로 계산하여 페이지를 렌더링하기에 서버 부하가 커지기 쉽다. 비용이 늘어나는 것도 마찬가지다.

SSG

적용

Page-DetailContainer

DetailContainer : 데이터 페칭 + UI

DetailContainer를 서버컴포넌트로 유지하고 데이터 페칭만을 담당시키려 함. ‘use client’는 각 하위 컴포넌트로 옮기기 시도

결과:

  • DetailContainer에서 클라이언트 사이드 데이터 상태관리를 위해 Tanstack query의 useQuery를 사용하고 있기 때문에 서버 컴포넌트를 유지할 수 없게 됨
  • 예: useQuery는 클라이언트 기능(React 훅)인데 서버 컴포넌트에서 직접 호출하려 하면 에러가 뜸

[ Server ] Error: Attempted to call useQuery() from the server but useQuery is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.

Page-DetailContainer-DetailPresenter

⇒ 컴포넌트 책임 분리

  • page.tsx: 서버 컴포넌트. 서버 사이드 로직과 초기 데이터 페칭
  • DetailContainer.tsx: 클라이언트 컴포넌트. 클라이언트 상태 관리와 데이터 업데이트를 담당
  • DetailPresenter.tsx: 상세페이지 UI 담당. 컴포넌트들을 렌더링

상세페이지를 SSR로 구현하는게 맞는가?

모임 상세 페이지의 특성상 CSR(Client Side Rendering)이 더 적합해 보입니다. 그 이유는 다음과 같습니다:

  1. 실시간성이 중요한 데이터
  • 참여자 수, 찜하기 상태, 모임 상태 등이 실시간으로 변경될 수 있음
  • 사용자 인터랙션(찜하기, 참여하기)에 따른 즉각적인 UI 업데이트 필요
  1. 개인화된 데이터 의존성
  • 로그인 상태에 따른 UI 변화
  • 사용자별 찜하기 상태
  • 참여 가능 여부 확인
  • 호스트 여부에 따른 UI 변화
  1. 빈번한 상태 업데이트
  • 찜하기/취소
  • 참여하기/취소
  • 리뷰 작성/수정
  • 모임 상태 변경

예를 들어, 모임 정원이 10명일 때:

[SSR의 경우]
1. A사용자가 페이지 로드 시점에 참여자 8명 확인
2. 실제로는 2명이 더 참여해서 10명이 됨
3. A사용자는 여전히 8명으로 보이는 상태에서 참여 시도
4. 버그 발생

[CSR의 경우]
1. A사용자 화면에 실시간으로 10명이 반영됨
2. 참여 버튼이 자동으로 비활성화
3. 사용자 경험 향상

결론

  1. CSR로 구현하는게 페이지 특성상 맞겠다는 생각이 들어 1차 구현과정에서는 CSR로 구현하였다.
  2. 이후 2차 리팩토링 기간에 페이지 초기 로드가 느리다는 피드백을 받아 최적화 중 SSR로 변환 -> 빠르게 상세페이지를 보여주기 위해서!
    이유: 이미지나 폰트 같이 무거운 리소스를 최적화하는 것이 중요한 페이지 특성상 서버 사이드 렌더링(Server Side Rendering)을 통해 성능 최적화
profile
👾

0개의 댓글