처음 만난 Next.js 문서


9.2

Server Components

지금부터는 Server Components의 개념과 렌더링 과정에 대해서 자세히 살펴보겠습니다.

Server Components는 2020년 12월에 React 팀을 통해 실험적인 기능으로 처음 발표되었으며, 이후 2024년 12월에 릴리즈된 React 버전19에서 정식 기능으로 출시되었습니다. 이 기능은 서버에서 컴포넌트를 렌더링하고 그 결과를 클라이언트로 전송함으로써 클라이언트 측의 렌더링 부담을 줄이고 성능을 향상시키는 데 도움을 주는 기능입니다. 그리고 Next.js의 App Router는 기본적으로 Server Components를 사용합니다. 그래서 개발자가 추가적인 구성을 하지 않고도 서버 렌더링을 자동으로 구현할 수 있고, 필요한 경우에는 'use client' 지시어를 사용하여 클라이언트 컴포넌트 형태로 사용할 수 있습니다.

9.2.1 Server Components의 렌더링 과정

React Server Components의 렌더링 과정에서는 아래 다이어그램과 같이 서버와 클라이언트가 함께 동작하여 사용자 인터페이스를 빠르고 효율적으로 구성하게 됩니다.

Server components rendering

먼저 사용자가 웹 애플리케이션을 방문하거나 특정 사용자 요청이 발생하면, 클라이언트는 서버에 컴포넌트를 렌더링하라는 요청을 보냅니다. 그러면 서버는 클라이언트에서 요청된 페이지나 컴포넌트를 인식하고, 해당 컴포넌트를 렌더링하기 시작합니다.

이후 서버는 요청된 React 컴포넌트를 렌더링하며, 이 과정에서 Server Components는 클라이언트 컴포넌트와 달리 서버에서 직접 실행됩니다. 서버 컴포넌트는 서버의 리소스(예: 데이터베이스 쿼리, 환경변수 등)에 접근할 수 있을 뿐만 아니라 외부 API 또한 직접 호출할 수 있기 때문에, 필요한 데이터를 직접 가져와 컴포넌트 내부에서 처리합니다.

서버에서는 React는 서버 컴포넌트를 React Server Component Payload (RSC Payload) 라는 특수 데이터 형식으로 렌더링합니다. 서버는 생성된 RSC Payload를 클라이언트로 전송하며, 클라이언트에서 컴포넌트를 재구성하는 데 사용됩니다.

클라이언트는 서버에서 전송된 RSC Payload를 파싱하고, 이 데이터를 기반으로 서버 컴포넌트의 UI를 재구성합니다. 그리고 자체적으로 렌더링한 클라이언트 컴포넌트와 결합하여 전체 UI를 완성하게 됩니다. 이렇게 사용자는 최종적으로 완성된 웹페이지를 보게 되며, 서버와 클라이언트 간의 협력을 통해 최적화된 성능과 빠른 로딩 속도를 제공할 수 있습니다.

참고로 서버 컴포넌트는 주로 정적인 내용을 처리하지만, 클라이언트 컴포넌트는 주로 사용자와의 상호작용(예: 클릭, 입력 등)을 처리합니다. 클라이언트 컴포넌트는 클라이언트 측에서 직접 렌더링되고, 상호작용에 반응하여 상태(state)를 업데이트하거나 새로운 요청을 서버로 보냅니다.

이처럼 React Server Components의 렌더링 과정은 서버와 클라이언트가 협력하여 UI를 효율적으로 구성하고, 서버에서 미리 렌더링된 컴포넌트를 클라이언트로 전송해 최종 UI를 빠르게 제공하는 방식이라고 이해하면 됩니다. Server Components를 사용하면 클라이언트 측의 부담을 줄일 수 있으며, 초기 로딩 시간을 크게 단축할 수 있습니다.

추가로 Server Components의 렌더링 과정에서 등장하는 RSC Payload는 아래와 같은 데이터들을 포함하는 JSON 형식의 스트림입니다.

  • 서버 컴포넌트의 렌더링된 결과
  • 클라이언트 컴포넌트가 렌더링될 위치에 대한 placeholder와 해당 자바스크립트 파일에 대한 참조
  • 서버 컴포넌트에서 클라이언트 컴포넌트로 전달된 모든 props

아래 그림은 실제 Server Components 렌더링 과정에서 생성된 RSC Payload를 캡처한 것입니다. 이러한 데이터가 서버에서 클라이언트로 전송되어 Server Components가 렌더링 된다고 보면 됩니다.

RSC Payload

9.2.2 Next.js에서의 Server Components 렌더링

그렇다면 Next.js에서는 React Server Components가 어떤 식으로 렌더링 될까요?

Next.js는 서버에서 React의 API를 사용하여 렌더링을 진행합니다. 렌더링 작업은 개별 Route Segment와 Suspense Boundaries에 의한 chunk로 나뉩니다.

각 chunk는 아래와 같은 두 단계로 렌더링됩니다.

  1. React는 서버 컴포넌트를 RSC Payload 데이터 형식으로 렌더링합니다.
  2. Next.js는 RSC Payload와 클라이언트 컴포넌트 자바스크립트 명령어를 사용해 서버에서 HTML을 렌더링합니다.

그런 다음 클라이언트에서는,

  1. HTML은 route의 빠른 non-interactive preview를 즉시 표시하는 데 사용되며, 이는 초기 페이지 로드에만 사용됩니다.
  2. RSC Payload는 클라이언트 및 서버 컴포넌트 트리를 조정하고 DOM을 업데이트하는 데 사용됩니다.
  3. 자바스크립트 명령어는 클라이언트 컴포넌트를 Hydration하고 애플리케이션을 interactive하게 만드는 데 사용됩니다.

NOTE. Hydration이란?
Hydration은 서버에서 렌더링된 HTML에 클라이언트 측 JavaScript를 연결하여 동적으로 동작하도록 만드는 과정입니다. 이벤트 리스너를 DOM에 연결하여 정적 HTML을 인터랙티브하게 만드는 과정인 것이죠. 쉽게 말하면 애플리케이션이 사용자와 상호작용 가능하도록(interactive) 만드는 과정이며, 백그라운드에서 Hydration은 hydrateRoot()라는 React API를 사용해서 수행됩니다.

9.2.3 Server Rendering Strategies

서버에서 렌더링이 이뤄지는 방식은 정적 렌더링, 동적 렌더링, 그리고 스트리밍 이렇게 세 가지로 나눌 수 있습니다. 지금부터 각 방식에 대해서 하나씩 살펴보도록 하겠습니다.

Static Rendering (정적 렌더링)

정적 렌더링을 사용하면 빌드 시 또는 데이터 재검증 후 백그라운드에서 Route가 렌더링됩니다. 결과는 캐시되어 콘텐츠 전송 네트워크(CDN)로 푸시될 수 있습니다. 이 최적화를 통해 사용자와 서버 요청 간에 렌더링 작업 결과를 공유할 수 있습니다.

정적 렌더링은 정적 블로그 게시물이나 제품 페이지와 같이 Route가 사용자 별로 개인화 되지 않고 빌드 시점에 알 수 있는 데이터가 있는 경우에 유용합니다.

Dynamic Rendering (동적 렌더링)

동적 렌더링을 사용하면 요청 시점에 각 사용자에 대해 Route가 동적으로 렌더링됩니다. 동적 렌더링은 Route에 사용자에게 맞춤화된 데이터가 있거나 쿠키 또는 URL의 검색 매개변수와 같이 요청 시점에만 알 수 있는 정보가 있는 경우에 유용합니다.

NOTE. 캐싱된 데이터가 있는 동적 경로
대부분의 웹사이트에서 Route는 완전히 정적이거나 완전히 동적인 것이 아니라 다양한 형태로 혼합되어 있습니다. 예를 들어, 주기적으로 재검증(Revalidate)되는 캐싱된 데이터를 보여주는 페이지와 캐싱되지 않은 개인화된 고객 데이터가 있는 페이지가 있을 수 있습니다.
Next.js에서는 캐싱된 데이터와 캐싱되지 않은 데이터가 모두 포함된 Dynamic Route를 가질 수 있습니다. RSC Payload와 데이터가 별도로 캐싱되기 때문에, 요청 시 모든 데이터를 가져올 때 성능에 미치는 영향에 대해 신경쓸 필요없이 동적 렌더링을 사용해도 됩니다.

Dynamic Rendering으로 전환되는 경우

Next.js는 렌더링 중에 Dynamic Functions(동적 함수) 또는 캐싱되지 않은 데이터 요청이 발견되면 전체 경로를 동적으로 렌더링하도록 전환합니다. 아래 표는 Dynamic Functions와 데이터 캐싱여부에 따라 Route가 정적으로 렌더링되는지 동적으로 렌더링되는지를 나타낸 것입니다.

Dynamic Functions Data Route
No Cached Statically Rendered
Yes Cached Dynamically Rendered
No Not Cached Dynamically Rendered
Yes Not Cached Dynamically Rendered

위 표에서 특정 Route가 완전히 정적으로 렌더링 되려면 모든 데이터가 캐싱되어야 한다는 것을 알 수 있습니다. 반대로 캐싱된 데이터 가져오기와 캐싱되지 않은 데이터 가져오기를 모두 사용하는 경우에는 해당 Route가 동적으로 렌더링됩니다. 다시 말하면, 동적 함수를 사용하거나 데이터가 캐싱되어 있지 않은 경우에는 동적으로 렌더링되는 것이죠.

Next.js를 사용해서 개발하게 되면 개발자가 렌더링 방식을 일일이 선택할 필요없이, 사용된 기능과 API에 따라 각 Route에 가장 적합한 렌더링 전략을 Next.js가 자동으로 선택합니다. 그래서 우리는 렌더링 전략에 대해서 신경쓰지 않고 개발을 하면 됩니다. 다만, 특정 데이터를 캐싱하거나 재검증할 시기를 선택하고 UI의 일부를 스트리밍하도록 선택적으로 구현할 수는 있습니다.

Dynamic APIs

Dynamic APIs는 사전 렌더링 시점에서는 알 수 없고 요청 시점에만 알 수 있는 정보에 의존합니다. 이러한 API를 사용하는 것은 개발자가 이를 의도적으로 사용하고 있음을 나타내며, 해당 API를 사용하는 전체 경로가 요청 시점에 동적으로 렌더링되도록 설정됩니다. Next.js에서 이러한 Dynamic APIs는 아래와 같습니다.

  • cookies(): 서버 컴포넌트에서 HTTP 요청의 쿠키를 읽을 수 있게 해주는 비동기 함수
  • headers(): 서버 컴포넌트에서 HTTP 요청의 헤더를 읽을 수 있게 해주는 비동기 함수
  • connection(): 렌더링 과정에서 사용자 요청을 기다려야 하는지 여부를 나타낼 수 있게 해주는 비동기 함수
  • draftMode(): Draft Mode(Headless CMS의 preview 콘텐츠를 미리 볼 수 있는 모드)를 활성화 또는 비활성화 할 수 있게 해주는 비동기 함수
  • searchParams prop

Streaming

Sequential parallel data fetching

스트리밍을 사용하면 서버에서 UI를 점진적으로 렌더링할 수 있습니다. 작업이 완료되는 대로 청크로 분할되어 클라이언트로 스트리밍됩니다. 이를 통해 사용자는 전체 콘텐츠의 렌더링이 완료되기 전에 페이지의 일부를 즉시 볼 수 있습니다.

Server rendering with streaming

스트리밍은 기본적으로 Next.js App Router에 내장되어 있습니다. 이를 통해 초기 페이지 로딩 성능을 향상시킬 수 있으며, 느린 데이터 페칭 때문에 전체 페이지 렌더링이 차단되는 현상을 개선할 수 있습니다.

9.2.4 서버 렌더링의 장점

웹 애플리케이션에서 일반적으로 사용하는 렌더링 방식은 클라이언트 렌더링입니다. 하지만 서버 렌더링을 사용하게 되면 아래와 같은 다양한 이점들을 얻을 수 있습니다.

빠른 Data Fetching

서버 컴포넌트(Server Components)는 데이터를 받아오는 과정을 서버에서 수행하기 때문에 데이터베이스 등의 실제 데이터 소스와 더 가까운 환경에서 데이터를 가져올 수 있습니다. 결과적으로 데이터를 가져오는데 걸리는 시간이 줄어들게 되고, 클라이언트에서 서버로 보내는 요청을 줄일 수 있기 때문에 성능을 향상시킬 수 있습니다.

보안성 향상

서버 컴포넌트를 사용하면 사용자 인증 토큰, API 키 등의 민감한 데이터와 로직을 서버에서만 관리함으로써 보안성을 향상 시킬 수 있습니다. 즉, 민감한 데이터를 클라이언트에 전혀 노출시키지 않고도 애플리케이션을 구현할 수 있습니다.

Caching

서버 컴포넌트를 통해 렌더링 된 결과는 캐싱을 통해 이후 동일한 요청에 대해 재사용할 수 있습니다. 캐싱을 통해 반복적인 렌더링 작업과 Data Fetching을 줄일 수 있으며, 이로 인해 성능을 향상시키고 컴퓨팅 비용을 절감할 수 있습니다.

성능 향상

서버 컴포넌트를 사용하면 클라이언트에서 필요한 JavaScript 번들 사이즈가 줄어들게 됩니다. 브라우저가 다운로드하고, 파싱하고, 실행해야 하는 클라이언트 사이드의 자바스크립트의 양이 줄어들기 때문입니다. JavaScript 번들 사이즈는 웹사이트에서 다운로드 받는 데이터의 용량에서 큰 부분을 차지하기 때문에 번들 사이즈를 줄이면 성능이 크게 향상될 수 있습니다. 또한 인터넷 속도가 느린 환경이나 성능이 좋지 않은 디바이스 등을 사용하는 경우에도 굉장히 유리합니다.

초기 페이지 로드 및 FCP(First Contentful Paint) 향상

서버 컴포넌트를 사용하면 서버에서 렌더링을 함으로써 클라이언트에서 별도의 렌더링 작업 없이 곧바로 페이지가 화면에 나오게 됩니다. 그래서 초기 페이지 로딩 속도가 빠르고, 웹 성능 지표 중의 하나인 FCP(First Contentful Paint)가 향상 됩니다. 참고로 FCP는 페이지 로딩 시점부터 웹페이지의 첫 콘텐츠가 화면에 표시될 때까지의 시간을 나타내는 성능 지표입니다.

검색 엔진 최적화(SEO)

서버 컴포넌트에서는 SSR(Server Side Rendering)을 통해 렌더링 된 HTML을 곧바로 클라이언트에 전달합니다. 이는 검색 엔진의 크롤링 봇이 페이지를 제대로 이해할 수 있게 하며, 결과적으로 검색 엔진 최적화에 유리합니다.

스트리밍(Streaming)

서버 컴포넌트는 렌더링 작업을 청크 단위로 분할하고 준비되는 대로 클라이언트로 스트리밍할 수 있게 해줍니다. 이를 통해 사용자는 전체 페이지가 서버에서 렌더링될 때까지 기다릴 필요 없이 페이지의 일부를 먼저 볼 수 있습니다.

이처럼 서버 렌더링을 사용하게 되면 다양한 이점들이 있기 때문에, Server Components를 사용할 수 있는 곳에서는 최대한 사용하는 것이 좋습니다.


마지막 업데이트: 2025년 10월 24일 02시 33분

이 문서의 저작권은 이인제(소플)에 있습니다. 무단 전재와 무단 복제를 금합니다.

On this page