iBetter Books
수정

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 페이로드라는 개념과, 서버에서 만든 컴포넌트가 브라우저로 어떻게 전달되는지 이해합니다.