Next.js 13 App Router가 등장하면서 컴포넌트의 실행 위치가 명시적으로 구분되기 시작했다. React Server Components(RSC)는 브라우저가 아닌 서버에서만 실행되는 컴포넌트로, 기존 SSR과는 근본적으로 다른 개념이다. App Router에서는 별도 선언이 없으면 모든 컴포넌트가 기본적으로 Server Component로 동작한다.
SSR은 서버에서 HTML을 생성하지만, 컴포넌트 코드는 클라이언트 번들에도 포함된다. RSC는 다르다 — 컴포넌트 코드 자체가 클라이언트로 전송되지 않는다.
[ 브라우저 요청 ]
|
v
[ Next.js 서버 ]
|
RSC 렌더링 실행
|
v
[ RSC Payload (직렬화된 UI 트리) ]
|
v
[ 브라우저: HTML + 최소 JS만 수신 ]
|
v
Client Components만 Hydration
브라우저는 RSC의 JavaScript 코드를 받지 않는다. 서버가 렌더링한 결과물(RSC Payload)만 전달된다.
App Router의 app/ 디렉토리 안에서 작성한 컴포넌트는 선언 없이도 Server Component다.
// app/posts/page.tsx
// 'use client' 없음 → Server Component
async function PostsPage() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
return (
<ul>
{posts.map((post: { id: number; title: string }) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
export default PostsPage;
async/await로 컴포넌트 내부에서 직접 데이터를 패칭할 수 있다. useEffect + useState 조합이 필요 없다.
RSC Payload — 서버가 렌더링한 컴포넌트 트리를 직렬화한 특수 포맷. JSON과 유사하지만 React가 내부적으로 사용하는 형식이다.
무거운 라이브러리를 Server Component에서만 사용하면 클라이언트 번들에 포함되지 않는다.
| 사용 위치 | date-fns 번들 포함 여부 |
|---|---|
| Client Component | 포함됨 (클라이언트로 전송) |
| Server Component | 미포함 (서버에서만 실행) |
// Server Component — date-fns는 클라이언트 번들에 들어가지 않는다
import { format } from 'date-fns';
export default function ArticleDate({ date }: { date: Date }) {
return <time>{format(date, 'yyyy년 MM월 dd일')}</time>;
}
Server Component는 브라우저 런타임이 없으므로 이벤트 핸들러를 붙일 수 없다. onClick, onChange 같은 상호작용이 필요한 경우 'use client'를 선언해 Client Component로 전환해야 한다.
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
Server Component와 Client Component를 혼합할 때는 Server → Client 방향으로만 컴포넌트를 중첩할 수 있다. Client Component 안에서 Server Component를 직접 import하면 그 Server Component는 Client Component로 처리된다.
| 필요한 기능 | 선택 |
|---|---|
| DB 쿼리, 파일 시스템 접근 | Server Component |
| 무거운 라이브러리 (번들 최적화) | Server Component |
onClick, onChange 등 이벤트 | Client Component |
useState, useEffect | Client Component |
브라우저 API (window, localStorage) | Client Component |
RSC는 서버와 클라이언트 경계를 명시적으로 관리함으로써 불필요한 JavaScript 전송을 줄이고, 데이터 패칭 로직을 컴포넌트에 직접 통합할 수 있게 한다.