인프런 "한 입 크기로 잘라먹는 Next.js" 수강
React Server Component(RSC)
서버에서만 실행되는 컴포넌트로, 브라우저에서는 전혀 실행되지 않는다.

Next.js의 App Router에서는 기본적으로 모든 컴포넌트가 서버 컴포넌트(Server Component)이다.
즉, 기본값은 서버 컴포넌트!
클라이언트 컴포넌트를 사용하고 싶다면, 컴포넌트 파일의 최상단에 'use client'를 선언해주면 된다.
'use client';
import { useEffect } from 'react';
import styles from './page.module.css';
export default function Home() {
useEffect(() => {}, []);
return <div className={styles.page}>인덱스 페이지</div>;
}
브라우저에서만 가능한 동작이 있다면 클라이언트 컴포넌트로 만든다.
useState, useEffect) 같은 것들은 브라우저 환경에서만 동작하는 것권장사항
구체적으로 구분하자면, “상호작용이 있어야 하면” 클라이언트 컴포넌트로 만들고 그렇지 않다면 서버 컴포넌트로 만들면 된다.
검색창은 사용자가 검색어를 입력하면 state가 변하기 떄문에 대표적인 클라이언트 컴포넌트이다.
'use client';
import React, { useState } from 'react';
export default function Searchbar() {
const [search, setSearch] = useState('');
const onChangeSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value);
};
return (
<div>
<input value={search} onChange={onChangeSearch} />
<button>검색</button>
</div>
);
}
이렇게 'use client'를 선언하면 Next.js가 해당 파일을 브라우저용으로 번들링해서 클라이언트에서 동작하도록 만들어준다.
컴포넌트를 어디에 둬야 할까?
App Router에서는 page.tsx, layout.tsx처럼 특별한 예약 파일명을 제외하면 그 외의 파일은 일반적인 JS/TS 파일로 간주한다.
그래서 app 폴더 안에서도 자유롭게 컴포넌트를 만들어둘 수 있다.
이러한 패턴을 Co-Location이라고 부른다.
즉, 페이지와 관련된 컴포넌트를 같은 위치에 함께 배치할 수 있다는 뜻이다! (ex. app/(with-searchbar)/search/components/Searchbar.tsx 등)
이러한 구조 덕분에 관련된 코드끼리 관리할 수 있어서 코드 가독성과 유지보수성에 도움이 된다.
서버 컴포넌트는 이름 그대로 서버에서만 실행된다.
그래서 아래와 같은 브라우저 전용 코드들은 포함될 수 없음!
useState, useEffect 등)이런 코드는 반드시 'use client'를 선언한 클라이언트 컴포넌트 안에서만 사용해야 한다.
서버 컴포넌트는 **"렌더링 결과만 반환하는 순수한 UI 정의 영역"
클라이언트 컴포넌트는 서버에서 한 번, 클라이언트에서 한 번, 총 두 번 실행된다.
| 실행 위치 | 실행 이유 |
|---|---|
| 서버 | 사전 렌더링(Pre-rendering)을 위해 |
| 클라이언트 | 하이드레이션(Hydration)을 위해 |
즉, 서버와 브라우저 양쪽에서도 실행된다!
import 하면 안 된다.클라이언트 컴포넌트는 앞서 말했듯 서버와 브라우저 모두에서 실행된다.
그런데 이 컴포넌트 안에서 서버 컴포넌트를 import 하게 되면 어떻게 될까?
❌ 브라우저에서도 서버 컴포넌트를 실행하려 하기 때문에 "런타입 에러"가 발생한다.
Next.js는 이런 경우를 해결하기 위해, 서버 컴포넌트를 자동으로 클라이언트 컴포넌트로 변환해버린다. 하지만 이건 일시적인 해결책으로 좋은 해결 방식은 아니다.
💡 권장 방식
클라이언트 컴포넌트에서 서버 컴포넌트를 직접import하지 말고,
props로ReactNode타입의children을 받아 렌더링하는 방식을 사용한다.
이 방식을 사용하면 서버 컴포넌트가 클라이언트 컴포넌트 안에서도 안전하게 렌더링된다.
서버 컴포넌트는 렌더링 결과를 직렬화(serialize)해서 클라이언트로 전달한다.
즉, 데이터는 문자열(JSON 형태)로 변환되어야 클라이언트 컴포넌트로 전달된다.
그런데 만약 직렬화 불가능한 데이터를 props로 전달한다면?
Symbol❌
Error: A function cannot be passed to a Client Component from a Server Component.
즉, 서버에서 "브라우저가 이해할 수 없는 형태의 데이터"를 넘기려 하면 에러가 발생한다.
import ClientComponent from './ClientComponent';
export default function ServerComponent() {
const handleClick = () => alert('Click!'); // ❌ 함수는 직렬화 불가
return <ClientComponent onClick={handleClick} />;
}
함수는 브라우저로 보낼 수 없기 때문에 에러가 발생한다.
✅ 해결 방법
브라우저에서 실행되어야 하는 로직이라면, 클라이언트 컴포넌트 내부에서 직접 정의애햐 한다.'use client'; export default function ClientComponent() { const handleClick = () => alert('클라이언트에서 실행!'); return <button onClick={handleClick}>클릭</button>; }
React Server Component의 순수한 결과물로, "서버 컴포넌트를 직렬화한 데이터 덩어리"로 볼 수 있다.
RSC Payload에는 서버 컴포넌트의 모든 데이터가 포함된다.
Props 값props 데이터즉, 서버는 RSC Payload를 만들어 브라우저에게 보내고, 브라우저는 그골 벋어 하이드레이션을 진행하며, "서버 + 클라이언트가 연결된 완전한 페이지"를 구성하는 것이다.