Ch 01. 주방과 홀의 이야기 — Server Components란
레스토랑의 두 공간
레스토랑에는 두 공간이 있습니다. 주방과 홀.
주방에서는 요리사가 재료를 손질하고, 조리하고, 접시에 담습니다. 손님은 주방에 들어올 수 없습니다. 주방에서 일어나는 일은 손님이 볼 수 없지만, 그 결과물(음식)은 손님에게 전달됩니다.
홀에서는 손님이 앉아 음식을 먹고, 종업원과 대화합니다. 여기서는 상호작용이 일어납니다. "물 한 잔 더 주세요", "계산해주세요" — 실시간으로 요청하고 응답받습니다.
Next.js의 Server Component와 Client Component가 바로 이 구조입니다.
Server Component = 주방. 서버에서만 실행됩니다. 데이터베이스에 직접 접근하고, 파일을 읽고, 비밀 키를 사용합니다. 이 모든 것이 사용자의 브라우저에 노출되지 않습니다. 결과물인 HTML만 사용자에게 전달됩니다.
Client Component = 홀. 브라우저에서 실행됩니다. 사용자의 클릭에 반응하고, 입력을 받고, 상태를 관리합니다. useState, useEffect, 이벤트 핸들러는 여기서만 사용할 수 있습니다.
App Router에서는 기본이 Server Component
App Router를 사용하면 모든 컴포넌트는 기본적으로 Server Component입니다. 파일 최상단에 "use client"를 선언해야만 Client Component가 됩니다.
// 파일: app/posts/page.tsx
// "use client"가 없으면 Server Component
export default async function PostsPage() {
// 이 코드는 서버에서 실행됩니다
// 데이터베이스에 직접 접근하거나 API를 호출할 수 있습니다
const posts = await fetch('https://api.example.com/posts').then((r) =>
r.json()
);
return (
<ul>
{posts.map((post: { id: number; title: string }) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
```text
```tsx
// 파일: app/components/LikeButton.tsx
'use client'; // 이 선언이 있어야 Client Component
import { useState } from 'react';
export default function LikeButton() {
const [liked, setLiked] = useState(false);
return (
<button onClick={() => setLiked((prev) => !prev)}>
{liked ? '좋아요 취소' : '좋아요'}
</button>
);
}
```text
### Server Component의 가장 큰 장점
**첫째, 자바스크립트 번들에 포함되지 않습니다.** Server Component의 코드는 서버에서만 실행되므로, 브라우저로 전송되는 자바스크립트에 포함되지 않습니다. 번들 크기가 줄고, 초기 로딩이 빨라집니다.
**둘째, 데이터베이스에 직접 접근할 수 있습니다.** API 서버를 경유하지 않고 데이터베이스에 직접 쿼리를 날릴 수 있습니다. 불필요한 네트워크 왕복이 없습니다.
**셋째, 비밀 키가 노출되지 않습니다.** API 키, 데이터베이스 비밀번호 같은 민감한 정보가 브라우저로 전송되지 않습니다. 서버에서만 사용하면 됩니다.
```tsx
// 파일: app/admin/page.tsx
// Server Component이므로 환경변수가 클라이언트에 노출되지 않습니다
import { db } from '@/lib/db';
export default async function AdminPage() {
// 데이터베이스에 직접 접근
const users = await db.user.findMany({
orderBy: { createdAt: 'desc' },
take: 20,
});
return (
<table>
<thead>
<tr>
<th>이름</th>
<th>이메일</th>
<th>가입일</th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<tr key={user.id}>
<td>{user.name}</td>
<td>{user.email}</td>
<td>{user.createdAt.toLocaleDateString()}</td>
</tr>
))}
</tbody>
</table>
);
}
다음 챕터에서는
다음 챕터에서는 Server Component가 실제로 어떻게 동작하는지 조금 더 깊이 살펴봅니다. RSC 페이로드라는 개념과, 서버에서 만든 컴포넌트가 브라우저로 어떻게 전달되는지 이해합니다.