서버 컴포넌트에서 데이터 페칭
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>
}
이 방식의 주요 이점은 다음과 같습니다.
- 서버에서 데이터를 페치하므로 초기 페이지 로드 시간이 빨라집니다.
- API 키 등의 민감한 정보를 클라이언트에 노출하지 않고 사용할 수 있습니다.
- 데이터 페칭 로직이 컴포넌트 로직과 함께 있어 관리가 용이합니다.
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를 서버 컴포넌트 렌더링
다음 요구사항을 만족하는 서버 컴포넌트를 구현해보세요.
- JSONPlaceholder API에서 포스트 목록을 가져옵니다.
- 가져온 포스트 목록을 렌더링합니다.
- 각 포스트에 대해 작성자 정보를 추가로 가져와 표시합니다.
- 데이터 로딩 중에는 로딩 UI를 표시합니다.
- 에러 처리를 구현합니다.
구현 예시
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를 사용하여 로딩 상태를 관리하는 방법을 경험할 수 있습니다.
이는 실제 애플리케이션에서 서버 컴포넌트를 효과적으로 활용하는 데 필요한 핵심 스킬입니다.