Next.js 15 (App Router 기준)에서는 RSC(React Server Components) 아키텍처가 기본적으로 적용되어 있습니다. 이 환경에서는 컴포넌트를 서버에서 실행할지, 클라이언트에서 실행할지 구분해야 하죠!
그런데 만약, 클라이언트 컴포넌트안에서 서버 컴포넌트를 import 하면, 다음과 같은 에러가 발생합니다.
 
평소처럼 개발하던 중 문득, "왜 클라이언트 컴포넌트안에서는 서버 컴포넌트를 import 할 수 없는걸까?" 하는 의문이 생겼고 그 이유에 대해 알아봤습니다.
React의 RSC 아키텍처는 단방향 렌더링 을 따릅니다.
1. 서버 단계
•	React가 서버 컴포넌트를 렌더링
•	RSC payload 생성 (직렬화된 컴포넌트 트리)
•	Next.js가 HTML과 함께 클라이언트로 전송
2. 클라이언트 단계
•	브라우저에서 HTML 표시
•	React가 hydration 및 reconciliation(재조정) 수행
•	클라이언트 컴포넌트 활성화
React는 화면을 만들 때 서버에서 먼저 컴포넌트를 실행하고, 그 다음에 클라이언트에서 이어서 처리하는 구조를 가지고 있습니다.
즉, 클라이언트 컴포넌트는 서버가 미리 만들어준 결과만 사용할 수 있고, 서버에서만 실행 가능한 컴포넌트 함수는 직접 실행할 수 없습니다.
| 서버 컴포넌트 | 클라이언트 컴포넌트 | |
|---|---|---|
| 실행 위치 | Node.js (서버) | 브라우저 (클라이언트) | 
| 접근 가능 API | DB, 파일 시스템, 서버 API | DOM, Window, LocalStorage | 
| 빌드 대상 | 서버 전용 번들 | 브라우저용 JS 번들 | 
| 렌더링 시점 | 요청 시 | hydration 이후 | 
클라이언트 컴포넌트가 서버 컴포넌트를 import한다는 건
브라우저 코드 안에서 Node.js 전용 코드를 실행하려는 시도가 되기 때문에, Next.js가 이를 감지하고 에러가 발생합니다.
브라우저 코드 안에서 Node.js 코드가 실행되지 않는 이유는?
➡️ 브라우저와 Node.js는 사용할 수 있는 API가 다르기 떄문입니다. 브라우저에는 파일 시스템, 데이터베이스, Node 전용 모듈이 없어서 서버 코드를 실행할 수 없어요!
React는 다음과 같은 구조로 컴포넌트 트리를 렌더링합니다.
React.createElement(ClientWrapper, {}, React.createElement(ServerContent))이 코드의 의미는 “ServerContent를 먼저 렌더링한 결과를 ClientWrapper의 children으로 전달한다” 입니다.
즉, 서버 컴포넌트는 이미 서버 단계에서 렌더링을 끝낸 후, 그 결과 (JSX Element)가 클라이언트 컴포넌트에 children으로 전달됩니다.
이때 중요한 점은 클라이언트 컴포넌트는 "컴포넌트 함수" 가 아니라, "렌더링된 결과(React Element)"를 받는다는 것입니다.
// 서버에서 ServerContent가 실행되어 JSX 결과물 생성
const serverResult = {
  type: 'div',
  props: {
    children: 'Hello from server!'
  }서버 컴포넌트는 서버에서 먼저 실행되어 JSX 결과물을 생성합니다.
{
  "type": "div",
  "props": {
    "children": "Hello from server!"
  }
}서버에서 렌더링된 결과는 RSC payload로 직렬화됩니다.
// 클라이언트에서는 이미 렌더링된 결과를 받음
function ClientWrapper({ children }) {
// children은 서버 컴포넌트가 아니라 렌더링된 React 엘리먼트
  console.log(children);
  // { type: 'div', props: { children: 'Hello from server!' } }
  
  return <div>{children}</div>
}
클라이언트 컴포넌트에서는 이미 렌더링된 React Element를 받습니다. 즉, 서버에서 만들어진 결과물을 그대로 표시합니다.
서버 컴포넌트를 클라이언트로 전달할 때 가장 중요한 점은 클라이언트는 결과물만 받을 수 있다 는 것입니다.
'use client'
import ServerComponent from './ServerComponent'
// 컴포넌트 함수 자체를 가져오려고 함
export default function ClientComponent() {
  return <ServerComponent />
  // 클라이언트에서 서버 컴포넌트 함수를 실행하려고 함
}
클라이언트에서 서버 컴포넌트 함수를 실행하려고 하면, 브라우저가 Node.js 전용 코드를 실행하려는 시도이기 때문에 에러가 발생합니다.
// 서버에서
<ClientWrapper>
  <ServerContent /> // 서버에서 실행되어 결과물 생성
</ClientWrapper>
// 클라이언트에서
'use client'
export default function ClientWrapper({ children }) {
// children은 이미 렌더링된 결과물 (React 엘리먼트)
  return <div>{children}</div>
}서버에서 먼저 컴포넌트를 실행하여 렌더링 결과(JSX)를 만들고, 이를 클라이언트 컴포넌트의 props(children)로 전달하면 안전하게 사용할 수 있습니다.
쉽게 비유하자면, 직접 import하는 것은 고객이 주방에 들어가 직접 요리 하려고 하는 것이고, props로 전달하는 것은 주방에서 요리를 완성한 후 완성된 음식을 고객에서 가져다 주는 것 입니다. 즉, 클라이언트 컴포넌트는 "완성된 음식"(렌더링 결과) 은 받을 수 있지만, "레시피"(서버 컴포넌트 함수) 로 직접 요리할 수는 없습니다!
평소에는 당연하게 사용했던 개념에 대해서 자세히 알아보고 정리해보면서 Next.js에 대해서 좀 더 깊이 알게된 것 같아요! 새로운 내용을 배우는 것도 중요하지만 내가 알고 있는 내용을 정확하고 깊게 이해하고 넘어가는 것도 중요하다는 것을 다시 한번 깨달았습니다...ㅎㅎ
기본을 알면 나중에 스스로 응용도 할 수 있을 거라고 생각합니다 🙌