icon안동민 개발노트

서버 컴포넌트에서 데이터 페칭


 Next.js의 서버 컴포넌트는 데이터 페칭을 위한 강력하고 효율적인 방법을 제공합니다.

 이 절에서는 서버 컴포넌트에서의 비동기 데이터 페칭 방법과 그 이점에 대해 자세히 알아보겠습니다.

async/await를 사용한 데이터 페칭

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

 이는 클라이언트 사이드 렌더링에서 흔히 사용하는 useEffect나 기타 훅을 사용할 필요가 없다는 것을 의미합니다.

 기본 예제

async function getData() {
  const res = await fetch('https://api.example.com/data')
  if (!res.ok) {
    throw new Error('Failed to fetch data')
  }
  return res.json()
}
 
export default async function Page() {
  const data = await getData()
  return <main>{/* data를 사용하여 UI 렌더링 */}</main>
}

 이 방식의 주요 이점은 다음과 같습니다.

  1. 서버에서 데이터를 페치하므로 초기 페이지 로드 시간이 빨라집니다.
  2. API 키 등의 민감한 정보를 클라이언트에 노출하지 않고 사용할 수 있습니다.
  3. 데이터 페칭 로직이 컴포넌트 로직과 함께 있어 관리가 용이합니다.

fetch API의 서버 사이드 캐싱

 Next.js는 fetch()에 대해 자동으로 서버 사이드 캐싱을 제공합니다.

 이를 통해 중복 요청을 줄이고 성능을 최적화할 수 있습니다.

 캐싱 옵션 예제

async function getData() {
  const res = await fetch('https://api.example.com/data', { next: { revalidate: 60 } })
  return res.json()
}

 이 예제에서 revalidate: 60은 60초마다 데이터를 재검증하도록 지정합니다.

 캐싱 옵션

  • { cache: 'force-cache' }: 기본값, 가능한 한 오래 캐시 (정적 데이터)
  • { cache: 'no-store' }: 항상 최신 데이터 페치 (동적 데이터)
  • { next: { revalidate: 10 } }: 10초마다 재검증 (ISR과 유사)

병렬 데이터 페칭

 여러 데이터 소스에서 동시에 데이터를 페치해야 할 경우, Promise.all이나 Promise.allSettled를 사용할 수 있습니다.

 병렬 데이터 페칭 예제

async function getUser(userId) {
  const res = await fetch(`https://api.example.com/users/${userId}`)
  return res.json()
}
 
async function getPosts(userId) {
  const res = await fetch(`https://api.example.com/posts?userId=${userId}`)
  return res.json()
}
 
export default async function UserProfile({ userId }) {
  const [user, posts] = await Promise.all([
    getUser(userId),
    getPosts(userId)
  ])
 
  return (
    <div>
      <h1>{user.name}</h1>
      <ul>
        {posts.map(post => <li key={post.id}>{post.title}</li>)}
      </ul>
    </div>
  )
}

 이 방식은 두 요청을 동시에 시작하여 전체 로딩 시간을 줄일 수 있습니다.

7장 서버, 클라이언트 컴포넌트와의 연결

 6장에서 다루는 서버 컴포넌트에서의 데이터 페칭은 7장의 서버, 클라이언트 컴포넌트 개념과 밀접하게 연관됩니다.

 서버 컴포넌트에서 데이터를 페치하고, 필요한 경우 이를 props를 통해 클라이언트 컴포넌트에 전달할 수 있습니다.

 이를 통해 서버 사이드 렌더링의 이점을 활용하면서도, 필요한 경우 클라이언트 사이드 인터랙션을 구현할 수 있습니다.

실습 : 외부 API를 서버 컴포넌트 렌더링

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

  1. JSONPlaceholder API에서 포스트 목록을 가져옵니다.
  2. 가져온 포스트 목록을 렌더링합니다.
  3. 각 포스트에 대해 작성자 정보를 추가로 가져와 표시합니다.
  4. 데이터 로딩 중에는 로딩 UI를 표시합니다.
  5. 에러 처리를 구현합니다.

 구현 예시

app/posts/page.js
import { Suspense } from 'react'
 
async function getPosts() {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts')
  if (!res.ok) throw new Error('Failed to fetch posts')
  return res.json()
}
 
async function getUser(userId) {
  const res = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
  if (!res.ok) throw new Error('Failed to fetch user')
  return res.json()
}
 
async function PostWithUser({ post }) {
  const user = await getUser(post.userId)
  return (
    <div>
      <h2>{post.title}</h2>
      <p>{post.body}</p>
      <p>Written by: {user.name}</p>
    </div>
  )
}
 
export default async function PostsPage() {
  const posts = await getPosts()
 
  return (
    <div>
      <h1>Posts</h1>
      {posts.map(post => (
        <Suspense key={post.id} fallback={<div>Loading post...</div>}>
          <PostWithUser post={post} />
        </Suspense>
      ))}
    </div>
  )
}

 이 실습을 통해 서버 컴포넌트에서 외부 API 데이터를 페치하고, 중첩된 데이터 요청을 처리하며, Suspense를 사용하여 로딩 상태를 관리하는 방법을 경험할 수 있습니다.

 이는 실제 애플리케이션에서 서버 컴포넌트를 효과적으로 활용하는 데 필요한 핵심 스킬입니다.