React Server Components (RSC)
React 18부터 새롭게 도입된 컴포넌트 유형으로, 서버에서만 실행되는 컴포넌트를 의미한다.
즉, 브라우저가 아닌 서버 환경에서 렌더링되는 컴포넌트다.
기존 React 컴포넌트와의 차이
일반적으로 사용하던 React 컴포넌트는 브라우저(Client)에서 실행된다.
- 사용자 인터랙션 처리
- 상태 관리 (useState)
- 생명주기 관리 (useEffect)
모든 로직이 브라우저에서 동작한다.
하지만 React Server Components는 다르다.
- 서버에서 실행
- 브라우저 JavaScript 번들에 포함되지 않음
- 클라이언트 리소스 사용 최소화
왜 RSC가 등장했을까?
기존 방식의 한계는 명확했다.
- 번들 크기 증가
- 초기 로딩 성능 저하
- 불필요한 클라이언트 JavaScript 실행
React Server Components는 이러한 문제를 해결하기 위해 등장했다.
클라이언트에서 실행되던 로직을 서버로 이동시켜 브라우저 부담을 줄이는 것이 핵심 목적이다.
특징
서버에서 직접 리소스 접근
RSC는 서버에서만 실행된다. 따라서 다음과 같은 작업이 자연스럽게 가능하다.
- 데이터베이스 직접 접근
- 파일 시스템 읽기
- 서버 전용 API 호출
export default async function Page() {
const data = await fetch('...');
const json = await data.json();
return <div>{json.title}</div>;
}클라이언트 번들 크기 감소
Server Component는 브라우저 JavaScript에 포함되지 않는다.
UI 결과만 클라이언트로 전달되고 실행 로직은 서버에 남는다.
Client Component와 조합
React Server Components는 단독으로만 사용하는 구조가 아니다.
필요한 경우 Client Component와 조합하여 사용한다.
'use client';
export default function LikeButton() {
const [liked, setLiked] = useState(false);
return (
<button onClick={() => setLiked(!liked)}>{liked ? '❤️' : '🤍'}</button>
);
}역할을 명확히 분리할 수 있다.
- 인터랙션, 상태 관리 → Client Component
- 데이터 페칭, 서버 로직 → Server Component
Next.js와 RSC
Next.js App Router는 Server Component를 기본값으로 사용한다.
export default function Page() {
return <h1>이 컴포넌트는 Server Component</h1>;
}별도 선언 없이 작성한 컴포넌트는 Server Component다.
'use client';
export default function Counter() {
const [count, setCount] = useState(0);
// ...
}Client Component가 필요한 경우에만 파일 최상단에 지시어'use client'를 선언한다.
정리
- React Server Components는 서버에서만 실행되는 컴포넌트다.
- 브라우저 번들 크기를 줄이고 성능을 개선한다.
- Next.js App Router는 RSC를 기본으로 사용한다.
- 인터랙션이 필요한 경우에만 Client Component를 사용한다.
Server Component에서 Hooks 사용 불가
Server Component는 서버 환경에서만 실행된다.
따라서 브라우저 환경을 전제로 하는 Hooks는 사용할 수 없다.
사용 불가한 Hooks
- useState
- useEffect
- useLayoutEffect
- useReducer
- 브라우저 전용 로직을 포함하는 커스텀 훅
Server vs Client 선택 기준
컴포넌트를 만들 때 어떤 유형으로 만들지 판단하는 기준은 간단하다.
사용자 상호작용이 필요한가?
- 필요하다 → Client Component
- 필요 없다 → Server Component
예를 들어, Searchbar 컴포넌트는 Client Component로 만들어야 한다.
'use client';
export default function SearchBar() {
const [query, setQuery] = useState('');
const router = useRouter();
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Enter') {
router.push(`/search?q=${query}`);
}
};
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={handleKeyDown}
placeholder='검색어를 입력하세요'
/>
);
}입력값 상태 관리, 이벤트 처리, 페이지 이동 등 브라우저 기능이 필요하기 때문이다.
Co-Location
Next.js App Router에서는 파일 이름이 page.tsx, layout.tsx 같은 예약어가 아니면 일반 파일로 취급된다.
app/
├── search/
│ ├── page.tsx # /search 라우트
│ ├── searchbar.tsx # 일반 컴포넌트
│ └── utils.ts # 유틸 함수관련된 컴포넌트와 로직을 같은 폴더에 배치하는 패턴을 Co-Location이라고 한다.
기능별로 파일을 가까이 두면 코드 탐색과 유지보수가 쉬워진다.
RSC 주의사항
Server Component 제약 Server Component에는 브라우저 전용 코드가 포함되면 안 된다.
ex. DOM 접근 (document, window), 이벤트 핸들러 (onClick, onChange), 브라우저 API (localStorage, sessionStorage)Client Component 실행 방식
Client Component는 브라우저에서만 실행되지 않는다.- 사전 렌더링 시 서버에서 1번 실행
- 하이드레이션 시 브라우저에서 1번 실행
즉, 서버와 클라이언트에서 모두 실행된다.
Import 제약
Client Component에서 Server Component를 직접 import 할 수 없다.- 필요한 경우, children으로 전달해야 한다.
Props 직렬화 제약
Server Component에서 Client Component로 전달하는 Props는 직렬화 가능한 값만 허용된다.
직렬화란? (Serialization)
복잡한 데이터 구조를 네트워크 전송이 가능한 단순한 형태(문자열, Byte)로 변환하는 과정이다.
// before - JavaScript 객체
const person = {
name: 'bin',
age: 10,
};// after
{"name":"bin","age":10}직렬화 가능한 값
- 문자열, 숫자, 불리언, null
- 배열, 객체 (직렬화 가능한 값만 포함) 등
직렬화 불가능한 값
- 함수
- 클래스 인스턴스
- Symbol
- undefined (객체 내부)
function func() {
console.log('함수는 직렬화 불가능');
}함수가 직렬화 불가능한 이유는 함수가 단순한 값이 아니라 실행 로직이며,
클로저와 렉시컬 스코프 등 실행 환경에 의존하기 때문이다.
Server Component에서 Client Component로 전달되는 Props는 네트워크를 거치므로 반드시 직렬화 가능해야 한다.
RSC Payload
Next.js의 사전 렌더링 과정에서 Server Component는 RSC Payload 형태로 직렬화된다.
이는 기존 HTML 기반 렌더링과 달리, 컴포넌트 트리 자체를 데이터 형태로 전달하는 방식이다.
RSC Payload에는 다음 정보가 포함된다.
- Server Component의 렌더링 결과 (UI 구조)
- Client Component의 위치 (어떤 컴포넌트가 어디에 들어가는지)
- Client Component에 전달되는 Props 값
브라우저는 이 RSC Payload를 받아 UI를 구성하고, 필요한 Client Component를 하이드레이션한다.
정리
- Server Component → 브라우저 코드 사용 불가
- Client Component → 서버 + 브라우저 모두 실행
- Server → Client Props → 직렬화 가능 값만 허용
- 함수는 Props로 전달 불가
- Next.js는 RSC Payload 기반으로 UI 구성