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

가장 먼저 명확히 해야할 점은 서버 컴포넌트 (RSC)는 서버사이드 렌더링(SSR)과는 완전히 다르다는 것이다. 둘다 ‘서버’라는 단어가 공통적으로 들어가지만, 목적과 동작 방식은 서로 다르다.
두 기술은 서로 다른 목적을 가지며, 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 페이로드라는 특별한 데이터 포맷에 있다.
사용자가 페이지를 요청하면, 서버에서 React가 렌더링을 시작한다.
/src/components/Counter.tsx 파일의 default export를 렌더링해줘" 와 같은 '모듈 참조(module reference)' 객체를 페이로드에 기록한다.
서버에서 생성된 이 RSC 페이로드는 완성될 때까지 기다리지 않고, 생성되는 즉시 스트림(Stream) 형태로 브라우저에 전송됩니다. 덕분에 사용자는 전체 페이지가 로드되기 전에도, 먼저 준비된 UI를 볼 수 있다.
브라우저의 React는 스트리밍되는 RSC 페이로드를 받아서 화면에 그리기 시작한다.

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와 같은 메타 프레임워크는 이 모든 복잡한 과정을 자동화해주므로, 개발자는 각 컴포넌트의 역할에만 집중하여 더 빠르고, 안전하며, 강력한 웹 애플리케이션을 만들 수 있다.