Next.js

⚙️ Next.js — 서버 컴포넌트 vs 클라이언트 컴포넌트

2025년 10월 15일

1️⃣ 서버 컴포넌트란?

정의

  • *서버 컴포넌트(Server Component)**는

👉 서버에서만 실행되는 React 컴포넌트다.

  • 브라우저로 JS 코드가 내려가지 않음
  • 실행 결과물만 (HTML + RSC Payload) 전달됨
// 기본적으로 서버 컴포넌트
export default function Page() {
return <h1>Hello Server Component</h1>;
}

📌 App Router에서는

**'use client'**를 붙이지 않으면 전부 서버 컴포넌트


2️⃣ 서버 컴포넌트는 어떻게 동작할까?

서버 컴포넌트 렌더링 흐름

  1. 사용자가 페이지 요청
  2. 서버에서 컴포넌트 실행
  3. API 호출 / DB 접근
  4. 데이터가 포함된 HTML 생성
  5. HTML + 최소한의 메타정보를 브라우저로 전달
요청 → 서버 실행 → 데이터 패칭 → HTML 생성 → 응답

👉 브라우저에서는 “이미 완성된 화면”을 받음


3️⃣ 서버 컴포넌트의 핵심 특징

✅ 할 수 있는 것

  • DB 직접 접근
  • 외부 API 호출 (비밀 키 사용 가능)
  • 서버 전용 로직 실행
  • 파일 시스템 접근
  • 무거운 연산 처리
// 서버에서만 실행됨
const users = await db.user.findMany();

❌ 할 수 없는 것

  • useState, useEffect
  • 클릭, 입력 이벤트
  • 브라우저 API (window, document)
  • 상태 변경 기반 UI

4️⃣ async / await를 쓰는 이유

export default async function Page() {
 const posts = await getPosts();
 return <PostListposts={posts} />;
}

이 의미는?

“posts가 준비될 때까지 HTML 생성을 멈춘다”

왜 그럴까?

  • 서버 컴포넌트는

    👉 HTML을 ‘완성된 상태’로 내려주는 구조

  • 중간에 “빈 화면 → 나중에 데이터 바인딩” ❌

📌 즉:

  • 서버 컴포넌트 = 데이터 포함 HTML
  • 클라이언트 컴포넌트 = HTML + JS로 나중에 상호작용

5️⃣ 클라이언트 컴포넌트란?

정의

  • *클라이언트 컴포넌트(Client Component)**는

👉 브라우저에서 실행되는 React 컴포넌트

'use client';
 
export default function Counter() {
 const [count, setCount] = useState(0);
 return <buttononClick={() => setCount(count + 1)}>{count}</button>;
}

📌 'use client'를 붙이는 순간:

  • 해당 파일

  • 그리고 그 하위 컴포넌트들

    → 전부 클라이언트 컴포넌트


6️⃣ 클라이언트 컴포넌트의 특징

✅ 할 수 있는 것

  • 상태 관리 (useState)
  • 이벤트 처리 (onClick)
  • 브라우저 API 사용
  • 애니메이션
  • 실시간 UI 반응

❌ 단점

  • JS 번들 크기 증가
  • 보안에 취약 (API Key 노출 위험)
  • 초기 로딩 비용 증가

7️⃣ 서버 vs 클라이언트 컴포넌트 비교

구분서버 컴포넌트클라이언트 컴포넌트
실행 위치서버브라우저
JS 다운로드
API / DB
useState
SEO👍 매우 좋음제한적
초기 로딩빠름상대적으로 느림
상호작용

8️⃣ 서버 컴포넌트 + 클라이언트 컴포넌트 조합

실전 패턴 (가장 중요)

// 서버 컴포넌트
export default async function Page() {
 const posts =awaitgetPosts();
 
return (
 <div>
  <PostListposts={posts} />   {/* 서버 */}
  <Counter />                  {/* 클라이언트 */}
 </div>
  );
}
// Counter.tsx
'use client';
 
export default function Counter() {
const [count, setCount] =useState(0);
return <buttononClick={() => setCount(count + 1)}>{count}</button>;
}

👉 데이터 & 보안 & SEO = 서버

👉 인터랙션 = 클라이언트


9️⃣ Suspense는 왜 필요할까?

서버 컴포넌트에서도 Suspense가 필요한 이유

  • 서버에서도 API 호출은 느릴 수 있음
  • 전체 페이지를 막지 않고
  • 부분별로 HTML 스트리밍
<Suspense fallback={<Skeleton />}>
<PostList />
</Suspense>

📌 효과:

  • 헤더/레이아웃 먼저 보여줌
  • 데이터 준비되면 해당 영역만 교체

🔟 generateMetadata와 서버 컴포넌트

export async function generateMetadata({ params }) {
const post = await getPostBySlug(params.slug);
 return {
	title: post.title,
  };
}
  • 서버에서 실행
  • SEO에 매우 중요
  • 페이지 렌더링 전에 실행됨

📌 그래서:

  • 메타데이터용 API 호출은 최상단에서
  • 화면 렌더링용 API는 하위 컴포넌트로 분리 가능

1️⃣1️⃣ 핵심 설계 철학 (이게 진짜 중요)

❌ “리액트니까 전부 클라이언트”

✅ “기본은 서버, 정말 필요한 부분만 클라이언트”

이렇게 생각하면 된다

  • 데이터 + 보안 + SEO → 서버 컴포넌트
  • 상태 + 이벤트 + UX → 클라이언트 컴포넌트