Next.js: 서버 컴포넌트 렌더링 프로세스
이전 글
이제 Next.js 렌더링 프로세스를 알아볼 시간..!

1. Next.js가 제공하는 3가지 서버 렌더링 전략 (Server Components와 함께)
**서버 컴포넌트(Server Component)**는 '서버 환경에서만 실행되는 컴포넌트'를 의미합니다.
이는 기존 React에서 사용되던 **클라이언트 컴포넌트(Client Component)**와는 구별되는 개념이며, 서버에서 UI 렌더링 및 선택적 캐싱 기능을 제공합니다.
서버 컴포넌트의 렌더링 방식을 자세히 살펴보기 전에, Next.js가 기본적으로 제공하는 3가지 서버 렌더링 전략을 먼저 알아보겠습니다.
전략 1: 정적 렌더링 (Static Rendering) - 기본값
Next.js의 서버 렌더링 전략 중 기본값입니다.
경로(Path)는 빌드 타임 또는 캐시가 무효화되어 데이터 재검증이 필요할 때 백그라운드에서 렌더링됩니다. 그 결과는 캐시되며, Content Delivery Network (CDN)에 배포될 수 있습니다.
CDN (Content Delivery Network): 분산된 서버 그룹으로 데이터 복사본을 저장하여, 최종 사용자와 가장 가까운 서버를 기준으로 데이터 요청을 처리할 수 있도록 합니다.
이 전략은 사용자에게 개인화되지 않은 데이터를 제공하며, 빌드 타임에 정적 경로를 알 수 있는 경우에 유용합니다.
예시: 블로그 게시물, 제품 상세 페이지
전략 2: 동적 렌더링 (Dynamic Rendering)
동적 렌더링은 경로가 요청 시 '각 사용자에게 맞게' 렌더링되는 전략입니다.
이는 정적 렌더링과 반대로, 사용자에게 개인화된 데이터가 있거나, 요청 시에만 알 수 있는 정보(쿠키 또는 URL의 검색 매개변수)가 있는 경로에 유용합니다.
참고: 모든 페이지가 완전히 정적이거나 동적일 필요는 없습니다. Next.js는 렌더링 과정에서 '동적 함수 호출' 또는 '캐시되지 않은 데이터 요청'이 발견되면, 전체 경로를 동적 렌더링으로 전환합니다. Next.js는 상황에 맞는 적절한 렌더링 전략(정적/동적)을 자동으로 선택하지만, 개발자는 캐시/재검증/UI 일부 스트리밍의 타이밍을 선택할 수 있습니다.
전략 3: 스트리밍 (Streaming)
이 전략은 아래 **'장점 7: 스트리밍'**에서 자세히 다루겠습니다.
2. 서버 컴포넌트 사용의 장점
서버 컴포넌트는 기존 클라이언트 컴포넌트와 비교하여 여러 가지 이점을 제공합니다.
장점 1: 데이터 페칭 성능 향상
데이터 소스에 더 가까운 서버에서 데이터를 페칭(Fetching) 할 수 있어, 데이터를 가져오는 시간과 요청 횟수를 줄일 수 있습니다.
장점 2: 보안 강화
민감한 데이터 및 로직(예: 토큰, API 키)을 클라이언트 환경에서 분리하여 서버에 유지함으로써 보안을 강화합니다.
장점 3: 강력한 캐싱 기능
서버에서 렌더링한 결과를 캐시하여, 이후 요청 및 사용자 간에 재사용할 수 있습니다. 이는 렌더링 및 데이터 페칭 작업의 양을 줄여줍니다.
Q: 클라이언트 캐싱과 서버 컴포넌트 캐싱의 차이점은 무엇인가요?
클라이언트 캐싱은 '개인 사용자'를 위한 것이지만, 서버 컴포넌트의 캐싱은 '전체 사용자'를 위한 공유 가능한 캐싱이라는 점이 가장 큰 차이점입니다.
Next.js 공식 문서에서 말하는 서버 컴포넌트 캐싱의 강력함을 3가지 핵심 포인트로 나누어 살펴보겠습니다.
1) '나만 빠름' vs. '모두가 빠름' (공유 가능성)
클라이언트 캐싱(React Query, SWR, 브라우저 캐시 등)은 해당 사용자의 브라우저에 저장됩니다.
- 클라이언트 캐싱: 사용자 A가 접속하여 데이터를 가져오면 캐시가 되지만, 사용자 B가 접속하면 다시 서버에서 데이터를 가져와야 합니다.
- 서버 컴포넌트 캐싱: 서버가 페이지를 렌더링하고 그 결과를 서버(또는 CDN)에 캐시해 둡니다. 사용자 A가 접속 후 캐시가 생성되면, 사용자 B는 렌더링이나 DB 조회를 다시 하지 않고 캐시된 결과를 즉시 받습니다.
핵심: 서버 캐싱은 한 번의 렌더링 결과를 수많은 사용자에게 재사용할 수 있습니다.
2) '재료 배달' vs. '완성품 배달' (캐싱의 대상)
클라이언트 캐싱과 서버 캐싱은 저장하는 **내용물(대상)**이 다릅니다.
| 비교 항목 | 클라이언트 캐싱 (Client Component) | 서버 컴포넌트 캐싱 (Server Component) |
|---|---|---|
| 저장 대상 | 데이터 (JSON) | 렌더링 결과 (RSC Payload / HTML) |
| 브라우저 역할 | JSON 데이터를 받아 컴포넌트 렌더링 및 화면 구성 (계산 수행) | 이미 완성된 UI 결과를 받아 화면에 표시만 함 |
| 부하 위치 | 사용자의 기기 (스마트폰, PC) | 서버 (미리 한 번만 수행) |
서버 캐싱은 데이터뿐만 아니라 '데이터를 HTML로 변환하는 연산 비용'까지 절약하여 저장합니다. 따라서 사용자의 기기 성능과 관계없이 화면이 매우 빠르게 로드됩니다.
3) 백엔드/DB 비용 절감 (비용 효율성)
서버 컴포넌트 캐싱이 적용되면, Next.js 서버는 이미 캐시된 결과가 있는지 확인하고, 있다면 데이터베이스(DB) 접근 없이 결과를 즉시 반환할 수 있습니다.
이로 인해 데이터베이스의 부하가 획기적으로 줄어들고, 클라우드 비용(API 호출 비용 등)을 크게 절감할 수 있습니다. 이것이 문서에서 말하는 비용을 절감하는 실질적인 의미입니다.
장점 4: 클라이언트 측 JavaScript (JS) 용량 감소
예를 들어, 비-인터랙티브(Non-interactive) 한 부분을 서버 컴포넌트로 옮기면 클라이언트 측에서 다운로드하고 파싱/실행해야 하는 JavaScript 용량을 줄일 수 있습니다.
이는 인터넷 속도가 느리거나 성능이 낮은 장치로 접근하는 사용자에게도 유리합니다.
장점 5: 초기 페이지 로드 및 FCP (First Contentful Paint) 개선
서버에서 HTML을 생성하여 전송하므로, 사용자가 페이지를 즉시 볼 수 있습니다.
클라이언트 컴포넌트처럼, 페이지를 렌더링하는 데 필요한 JS를 다운로드하고 파싱/실행할 필요가 없습니다.
장점 6: 검색 엔진 최적화 (SEO) 및 소셜 공유 개선
이미 렌더링이 완료된 HTML은 검색 엔진 봇이 페이지를 인덱싱하고 소셜 카드 미리보기를 생성하는 데 유리합니다.
장점 7: 스트리밍 (Streaming)
스트리밍은 Next.js App Router에 기본적으로 내장된 기능입니다.
loading.js파일 및React Suspense를 사용하여 경로 세그먼트의 스트리밍을 시작할 수 있습니다.
서버 컴포넌트의 스트리밍이란 무엇일까요?
서버에서 렌더링된 페이지의 HTML을 한 번에 보내지 않고, 데이터 준비가 끝나는 대로 작은 조각(Chunk)으로 나누어 클라이언트에 점진적으로 전송하는 기술입니다.
기존 SSR 방식은 페이지를 구성하는 데 시간이 오래 걸리는 작업이 있으면, 다른 컴포넌트들도 함께 대기해야 하는 블로킹(Blocking) 문제가 있었습니다.
페이지 렌더링 작업 순서: 데이터 페칭 (API/DB 호출) -> HTML 생성 (렌더링) -> HTML, CSS, JS 클라이언트로 전송 -> 하이드레이션 (상호작용 가능하게 만들기)
Next.js는 이 **렌더링 작업(Rendering Work)**을 **청크(Chunk)**로 분할하여, 준비되는 대로 클라이언트에게 스트리밍하는 방식을 사용합니다.
이를 통해, 전체 페이지가 서버에서 렌더링 완료될 때까지 기다리지 않고도, 사용자가 페이지의 일부를 먼저 볼 수 있습니다.
3. 서버 컴포넌트 렌더링 과정 (Rendering Work)
서버 컴포넌트가 최종적으로 화면에 표시되기까지의 과정을 서버와 클라이언트 단계로 나누어 자세히 살펴보겠습니다.
서버 환경에서 Next.js는 React의 API를 활용하여 렌더링 과정을 조정합니다.
전체 과정 요약
- 서버: 렌더링 작업을 청크로 분할.
- 서버: 각 청크별로, React는 서버 컴포넌트를 RSC 페이로드(Payload) 데이터 형식으로 렌더링합니다.
- 서버: 각 청크별로, Next.js는 RSC 페이로드와 클라이언트 컴포넌트 JS 지침에 따라 HTML을 렌더링하여 클라이언트에 스트리밍합니다.
- 클라이언트: HTML을 사용하여 비-인터랙티브 미리보기를 즉시 표시합니다 (초기 페이지 로드 시).
- 클라이언트: RSC 페이로드를 사용하여 **컴포넌트 트리(Server/Client)**를 **재조정(Reconcile)**하고 DOM을 업데이트합니다.
- 클라이언트: JS 지침에 따라 클라이언트 컴포넌트를 **하이드레이션(Hydration)**하여 앱을 인터랙티브하게 만듭니다.
1. 서버 - 렌더링 작업(Rendering Work)을 청크(Chunk)로 분할
렌더링 작업은 다음 두 가지 기준에 따라 청크로 분할됩니다.
- 개별 경로 세그먼트: App Router에서 URL 경로의 각 부분을 나타내는 폴더.
- Suspense 경계(Boundaries):
React Suspense컴포넌트가 감싸고 있는 부분.
분할된 청크는 각각 두 단계를 거쳐 서버에서 렌더링됩니다.
2. 서버 - React는 서버 컴포넌트를 RSC 페이로드로 렌더링 (각 청크별)
**RSC 페이로드(Payload)**는 '렌더링된 React 컴포넌트 트리의 간결한 이진 표현'이며, 브라우저가 DOM을 업데이트하는 데 사용됩니다.
RSC 페이로드는 다음과 같은 핵심 정보를 포함합니다.
- 서버 컴포넌트의 렌더링 결과.
- 클라이언트 컴포넌트가 렌더링되어야 할 위치(Placeholder) 및 관련 JS 파일에 대한 참조.
- 서버 컴포넌트에서 클라이언트 컴포넌트로 전달된 모든
props.
이처럼 서버 컴포넌트는 React에 의해 렌더링 결과 및 여러 정보를 담은 페이로드 데이터 형식으로 변환됩니다.
3. 서버 - Next.js는 HTML을 렌더링 (각 청크별)
Next.js는 RSC 페이로드와 클라이언트 컴포넌트 JS 로드 및 하이드레이션 지침에 따라 HTML을 렌더링합니다. 이 HTML은 초기 페이지 로드 시 사용자에게 표시될 내용을 담고 있습니다.
예시 (지침):
- 어떤 클라이언트 컴포넌트를 로드해야 하는지, 해당 컴포넌트의 JS 번들은 어디에 있는지 명시합니다.
- 서버에서 미리 렌더링된 HTML과 클라이언트 JS를 연결하는 하이드레이션 작업의 기준을 제공합니다.
4. 클라이언트 - 비-인터랙티브 HTML 미리보기 즉시 표시
서버에서 최종적으로 렌더링되어 스트리밍된 HTML을 화면에 즉시 표시합니다. 이 HTML은 정적이며, 클라이언트 컴포넌트를 포함한 JavaScript 코드가 하이드레이션되기 전 상태이므로 비-인터랙티브합니다.
5. 클라이언트 - 컴포넌트 트리 재조정(Reconcile) 후 DOM 업데이트
RSC 페이로드에 포함된 정보(클라이언트 컴포넌트의 위치, props 등)를 사용하여 서버 컴포넌트와 클라이언트 컴포넌트로 구성된 React 컴포넌트 트리를 **재조정(Reconcile)**합니다.
이 재조정을 통해 변경된 사항이 DOM에 반영되어 화면이 업데이트됩니다.
6. 클라이언트 - 하이드레이션 (Hydration)
마지막으로, HTML을 인터랙티브하게 만드는 단계입니다. 이벤트 핸들러와 같은 JavaScript 코드가 DOM에 연결되어(Attach) 상호작용이 가능한 동적인 웹 페이지가 완성됩니다.
실습
어떤 서버 컴포넌트 기반 페이지에서, 다음과 같은 클라이언트 컴포넌트가 있다면,
HTML preview단계에서는 어떻게, 컴포넌트 트리 단계에서는 어떻게, 하이드레이션 단계에서는 최종적으로 어떻게 보여질 지 예측해보세요
jsx"use client"; import { useState } from "react"; export default function ClientComponent() { const [value, setValue] = useState(0); return ( <div> ClientComponent <button onClick={() => { console.log("test string"); setValue((prev) => prev + 1); }} > Click me </button> <p>Value: {value}</p> </div> ); }
참고 자료
아직 댓글이 없습니다
첫 번째 댓글을 작성해보세요!