React 18에서 처음 공개된 서버 컴포넌트(React Server Components, RSC)는 이제 Next.js App Router의 기본 컴포넌트 모델이 되었다.서버 컴포넌트는 React 애플리케이션을 만드는 방식을 근본적으로 바꾸고, 성능과 사용자 경험을 크게 향상시킬 수 있는 강력한 패러다임이다.

🥊 서버 컴포넌트(RSC) vs 서버 사이드 렌더링(SSR)
가장 먼저 명확히 해야할 점은 서버 컴포넌트 (RSC)는 서버사이드 렌더링(SSR)과는 완전히 다르다는 것이다. 둘다 ‘서버’라는 단어가 공통적으로 들어가지만, 목적과 동작 방식은 서로 다르다.
- SSR: 서버와 클라이언트 컴포넌트를 구분하지 않고, 모든 컴포넌트를 서버에서 순수한 HTML 문자열로 만들어 브라우저에 보낸다. 주된 목적은 빠른 첫 화면 로딩 (FCP)과 검색 엔진 최적화(SEO)이다.
- RSC: 서버 컴포넌트만 서버에서 실행해서, HTML이 아닌 RSC 페이로드 (Payload)라는 특별한 데이터 포맷으로 만든다. 주된 목적은 JavaScript 번들 사이즈 감소와 효율적인 데이터 로딩이다.
두 기술은 서로 다른 목적을 가지며, Next.js 에서는 이 둘을 함께 사용하여 최고의 성능을 이끌어낼 수 있다.
🍀 서버 컴포넌트를 사용하는 이유 (주요 특징 및 장점)
-
클라이언트 JavaScript 번들 사이즈 감소
서버 컴포넌트는 코드의 브라우저로 전송되지 않기 때문에 클라이언트가 다운로드 해야할 JavaScript의 양이 줄어든다.
예를 들어, 날짜 포맷팅 라이브러리
date-fns처럼 무거운 라이브러리를 서버 컴포넌트에서만 사용하면, 해당 라이브러리 코드는 클라이언트 번들에 포함되지 않아 초기 페이지 로딩 속도가 비약적으로 빨라진다. -
서버 자원에 직접 접근
데이터베이스, 내부 API, 파일 시스템 같은 서브 측 자원에 직접 접근할 수 있으며, API 키 같은 민감한 데이터를 안전하게 보관할 수 있다.
-
async/await를 사용한 직접적인 데이터 페칭서버 컴포넌트는 컴포넌트 자체를
async함수로 선언할 수 있다. 이로 인해useEffect나useState같은 훅(hook) 없이도, 컴포넌트 최상단에서 바로 데이터를 가져오는 것이 가능하다.// app/posts/page.tsx (서버 컴포넌트) async function getPosts() { const res = await fetch('https://.../posts'); return res.json(); } // 컴포넌트 자체가 async 함수 export default async function PostsPage() { const posts = await getPosts(); return ( <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ); } -
자동 캐싱 및 렌더링 최적화
서버 컴포넌트의 렌더링 결과는 Next.js에 의해 자동으로 캐싱된다. 동일한 요청에 대해서는 다시 렌더링할 필요 없이 캐시된 결과를 사용하기 때문에 응답 속도가 매우 빠르다.
🍀 서버 컴포넌트는 '어떻게' 동작할까?
서버 컴포넌트 내부 동작의 핵심은 RSC 페이로드라는 특별한 데이터 포맷에 있다.
1단계: 서버 렌더링 (RSC 페이로드 생성)
사용자가 페이지를 요청하면, 서버에서 React가 렌더링을 시작한다.
- 서버 컴포넌트를 만나면: 데이터베이스 조회 같은 서버 작업을 수행하고, 그 결과를 HTML이 아닌 RSC 페이로드에 기록한다.
- 클라이언트 컴포넌트를 만나면: 렌더링하지 않고, 대신 "이 자리에는
/src/components/Counter.tsx파일의default export를 렌더링해줘" 와 같은 '모듈 참조(module reference)' 객체를 페이로드에 기록한다.

2단계: 데이터 스트리밍
서버에서 생성된 이 RSC 페이로드는 완성될 때까지 기다리지 않고, 생성되는 즉시 스트림(Stream) 형태로 브라우저에 전송됩니다. 덕분에 사용자는 전체 페이지가 로드되기 전에도, 먼저 준비된 UI를 볼 수 있다.
3단계: 클라이언트 렌더링 (조립 및 하이드레이션)
브라우저의 React는 스트리밍되는 RSC 페이로드를 받아서 화면에 그리기 시작한다.
- 서버 컴포넌트의 결과물은 그대로 화면에 표시한다.
- 클라이언트 컴포넌트의 '모듈 참조'를 만나면, 해당 컴포넌트의 JavaScript 코드를 다운로드해서 그 자리에 렌더링하고, 상호작용이 가능하도록 이벤트를 연결(하이드레이션)한다.

🤔 언제 서버 컴포넌트를 쓰고, 언제 클라이언트 컴포넌트를 써야할까?
Next.js 공식 문서에서는 해당 이슈에 대한 확실한 해결책을 제공한다. 기본적으로 Next.js의 모든 컴포넌트는 서버 컴포넌트로 시작하고, 꼭 필요한 경우에만 "use client";를 추가하여 클라이언트 컴포넌트로 전환하는 것을 권장한다.
기능별 컴포넌트 선택
| 기능 | 서버 컴포넌트 | 클라이언트 컴포넌트 |
|---|---|---|
| 데이터 페칭 | ✅ (권장) | ❌ |
| 백엔드 자원 직접 접근 | ✅ (권장) | ❌ |
| 민감 정보 보관 (토큰, API 키) | ✅ (권장) | ❌ |
| 무거운 의존성 사용 | ✅ (권장) | ❌ |
사용자 이벤트 처리 (onClick, onChange) | ❌ | ✅ (필수) |
상태 관리 (useState, useReducer) | ❌ | ✅ (필수) |
라이프사이클 훅 (useEffect) | ❌ | ✅ (필수) |
브라우저 전용 API (window, localStorage) | ❌ | ✅ (필수) |
| Context API (상태 관리) | ❌ | ✅ (필수) |
| 커스텀 훅 (훅 사용) | ❌ | ✅ (필수) |
🍀 서버와 클라이언트의 공존: 제약 사항과 패턴
서버 컴포넌트는 서버에서만 렌더링되므로, useState, useEffect 같은 훅이나 onClick 같은 이벤트 리스너를 사용할 수 없다. 이러한 인터랙티브한 기능이 필요할 때, 우리는 파일 최상단에 "use client"; 지시어를 추가하여 클라이언트 컴포넌트를 사용한다.
여기서 중요한 규칙은, 클라이언트 컴포넌트는 서버 컴포넌트를 직접 import할 수 없다는 것이다. 서버 컴포넌트의 코드는 브라우저로 전송되지 않기 때문이다.
하지만 컴포지션(Composition), 즉 children prop을 통해 서버 컴포넌트를 전달받아 렌더링하는 것은 가능하다.
// OuterServerComponent.tsx (서버 컴포넌트)
import ClientComponent from './ClientComponent';
import ServerComponent from './ServerComponent';
export default function OuterServerComponent() {
// ClientComponent에 ServerComponent를 자식으로 전달
return (
<ClientComponent>
<ServerComponent />
</ClientComponent>
);
}
이러한 패턴을 통해 우리는 서버와 클라이언트의 장점을 모두 취하는 유연한 컴포넌트 트리를 구성할 수 있다.
✏️ 결론
서버 컴포넌트는 단순히 서버에서 렌더링하는 기술을 넘어, '일은 서버에서 하고, 결과물에 대한 설계도(RSC 페이로드)만 클라이언트에 보내주는' 새로운 패러다임이다. Next.js와 같은 메타 프레임워크는 이 모든 복잡한 과정을 자동화해주므로, 개발자는 각 컴포넌트의 역할에만 집중하여 더 빠르고, 안전하며, 강력한 웹 애플리케이션을 만들 수 있다.