icon안동민 개발노트

서버 컴포넌트 이해하기


 React 서버 컴포넌트는 서버 사이드 렌더링(SSR)을 한 단계 더 발전시킨 기술로, Next.js에서 기본적으로 지원됩니다.

 이 절에서는 서버 컴포넌트의 개념, 장점, 그리고 Next.js에서의 구현 방식에 대해 자세히 알아보겠습니다.

서버 컴포넌트의 개념

 서버 컴포넌트는 서버에서 렌더링되고 실행되는 React 컴포넌트입니다.

 이는 클라이언트로 전송되는 JavaScript 번들 크기를 줄이고, 서버의 리소스를 직접 활용할 수 있게 해줍니다.

서버 컴포넌트의 특징

  1. 자동 코드 분할 : 서버 컴포넌트는 자동으로 코드를 분할하여 필요한 부분만 클라이언트로 전송합니다.
  2. 직접적인 백엔드 리소스 접근 : 데이터베이스나 파일 시스템에 직접 접근할 수 있습니다.
  3. 보안 : 민감한 정보를 클라이언트에 노출하지 않고 사용할 수 있습니다.
  4. 캐싱 : 서버에서 렌더링된 결과를 캐시하여 성능을 향상시킬 수 있습니다.

성능 이점

  1. 초기 로드 시간 감소 : 클라이언트로 전송되는 JavaScript 양이 줄어 초기 로드 속도가 향상됩니다.
  2. 서버 리소스 활용 : 강력한 서버 리소스를 활용하여 복잡한 연산을 수행할 수 있습니다.
  3. 네트워크 워터폴 방지 : 여러 번의 네트워크 요청 없이 한 번에 필요한 데이터를 가져올 수 있습니다.

데이터 접근 방식

 서버 컴포넌트에서는 async/await를 직접 사용하여 데이터를 가져올 수 있습니다.

app/users/page.js
async function getUsers() {
  const res = await fetch('https://api.example.com/users')
  return res.json()
}
 
export default async function UsersPage() {
  const users = await getUsers()
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

 이 예제에서 UsersPage 컴포넌트는 서버에서 실행되며, 클라이언트로 전송되기 전에 사용자 목록을 가져와 렌더링합니다.

일반적인 사용 사례

  1. 데이터 페칭이 필요한 페이지 : 서버에서 직접 데이터를 가져와 렌더링할 수 있습니다.
  2. SEO가 중요한 페이지 : 서버에서 완전히 렌더링된 HTML을 제공하여 SEO를 개선할 수 있습니다.
  3. 대규모 의존성이 있는 컴포넌트 : 클라이언트 번들 크기를 줄일 수 있습니다.
app/dashboard/page.js
import { Chart } from 'heavy-chart-library'
 
async function getData() {
  // 데이터 페칭 로직
}
 
export default async function DashboardPage() {
  const data = await getData()
  return <Chart data={data} />
}

 이 예제에서 heavy-chart-library는 서버에서만 로드되어 클라이언트 번들 크기를 줄입니다.

6장 데이터 페칭과의 연결

 7장의 서버 컴포넌트 개념은 6장에서 다룬 데이터 페칭 방식과 밀접하게 연관됩니다.

 서버 컴포넌트를 사용하면 6장에서 배운 fetch API나 데이터베이스 쿼리를 직접 컴포넌트 내에서 사용할 수 있으며, 이는 SSR, SSG, ISR 등 다양한 렌더링 전략과 결합하여 사용될 수 있습니다.

Next.js에서의 구현

 Next.js 13 이상에서는 app 디렉토리 내의 모든 컴포넌트가 기본적으로 서버 컴포넌트로 취급됩니다.

 클라이언트 컴포넌트로 전환하려면 파일 상단에 'use client' 지시어를 추가해야 합니다.

// 서버 컴포넌트 (기본값)
export default function ServerComponent() {
  return <h1>This is a server component</h1>
}
 
// 클라이언트 컴포넌트
'use client'
 
export default function ClientComponent() {
  return <h1>This is a client component</h1>
}

실습 : 서버 컴포넌트 구현

 다음 요구사항을 만족하는 서버 컴포넌트를 구현해보세요.

  1. 사용자 목록을 데이터베이스에서 직접 가져옵니다.
  2. 가져온 사용자 목록을 테이블 형태로 렌더링합니다.
  3. 각 사용자의 이름, 이메일, 가입일을 표시합니다.
  4. 에러 처리를 구현합니다.

 구현 예시

app/users/page.js
import { sql } from '@vercel/postgres'
 
async function getUsers() {
  try {
    const { rows } = await sql`SELECT * FROM users ORDER BY created_at DESC`
    return rows
  } catch (error) {
    console.error('Failed to fetch users:', error)
    throw new Error('Failed to fetch users')
  }
}
 
export default async function UsersPage() {
  let users
  try {
    users = await getUsers()
  } catch (error) {
    return <div>Error: {error.message}</div>
  }
 
  return (
    <div>
      <h1>User List</h1>
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Email</th>
            <th>Joined</th>
          </tr>
        </thead>
        <tbody>
          {users.map(user => (
            <tr key={user.id}>
              <td>{user.name}</td>
              <td>{user.email}</td>
              <td>{new Date(user.created_at).toLocaleDateString()}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  )
}

 이 실습을 통해 서버 컴포넌트에서 직접 데이터베이스에 접근하여 데이터를 가져오고 렌더링하는 과정을 경험할 수 있습니다.

 이는 백엔드 API를 거치지 않고도 데이터를 효율적으로 가져올 수 있는 서버 컴포넌트의 강력한 기능을 보여줍니다.

 서버 컴포넌트는 Next.js 애플리케이션의 성능과 개발 경험을 크게 향상시킬 수 있는 강력한 도구입니다.

 클라이언트 컴포넌트와 적절히 조합하여 사용하면, 서버의 강력한 기능과 클라이언트의 동적인 상호작용을 모두 활용할 수 있는 최적의 웹 애플리케이션을 구축할 수 있습니다.