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️⃣ 서버 컴포넌트는 어떻게 동작할까?
서버 컴포넌트 렌더링 흐름
- 사용자가 페이지 요청
- 서버에서 컴포넌트 실행
- API 호출 / DB 접근
- 데이터가 포함된 HTML 생성
- 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 → 클라이언트 컴포넌트