번역 자료 / / 2025. 5. 24. 10:48

[nextjs 번역] 데이터 패칭(Fetching Data)

원문: Getting Started: Fetching Data | Next.js


Next.js에서는 서버 컴포넌트와 클라이언트 컴포넌트 모두에서 데이터를 패칭할 수 있습니다. 서버 컴포넌트에서 데이터를 패칭하면 보안, 성능, SEO 측면에서 이점이 있습니다.

서버 컴포넌트에서 데이터 패칭하기

서버 컴포넌트(기본값)에서는 fetch를 비롯한 비동기 데이터 패칭을 자유롭게 사용할 수 있습니다. 예시:

// app/page.tsx
export default async function Page() {
  const res = await fetch('https://api.example.com/data')
  const data = await res.json()
  return <div>{data.title}</div>
}

클라이언트 컴포넌트에서 데이터 패칭하기

클라이언트 컴포넌트(상단에 'use client' 선언)에서는 React의 훅(useEffect, useState) 또는 SWR, React Query 등 클라이언트 데이터 패칭 라이브러리를 사용할 수 있습니다.

// app/ui/client-component.tsx
'use client'
import { useEffect, useState } from 'react'

export default function ClientComponent() {
  const [data, setData] = useState(null)
  useEffect(() => {
    fetch('/api/data')
      .then((res) => res.json())
      .then(setData)
  }, [])
  return <div>{data ? data.title : 'Loading...'}</div>
}

fetch 함수의 확장 기능

Next.js의 fetch는 기본적으로 캐싱, 재검증, 동시성 제어 등 다양한 기능을 내장하고 있습니다. 예를 들어, cache: 'no-store' 옵션을 주면 항상 최신 데이터를 가져옵니다.

const res = await fetch('https://api.example.com/data', { cache: 'no-store' })

스트리밍(Streaming)과 Suspense

Next.js는 React의 Suspense와 streaming을 활용해 데이터 패칭 중에도 UI를 점진적으로 렌더링할 수 있습니다.

loading.js 파일로 전체 페이지 스트리밍

특정 라우트(예: app/blog/page.js)에 loading.js 파일을 추가하면, 데이터 패칭 중에도 레이아웃과 로딩 UI가 먼저 보여지고, 데이터가 준비되면 자동으로 교체됩니다.

// app/blog/loading.tsx
export default function Loading() {
  return <div>Loading...</div>
}

로 부분 스트리밍

<Suspense>를 사용하면 페이지의 일부만 스트리밍할 수 있습니다. 예를 들어, 블로그 리스트만 별도로 로딩 UI와 함께 스트리밍할 수 있습니다.

// app/blog/page.tsx
import { Suspense } from 'react'
import BlogList from '@/components/BlogList'
import BlogListSkeleton from '@/components/BlogListSkeleton'

export default function BlogPage() {
  return (
    <div>
      <header>
        <h1>Welcome to the Blog</h1>
        <p>Read the latest posts below.</p>
      </header>
      <main>
        <Suspense fallback={<BlogListSkeleton />}>
          <BlogList />
        </Suspense>
      </main>
    </div>
  )
}

의미 있는 로딩 상태 만들기

즉각적인 로딩 상태는 사용자가 네비게이션 후 바로 볼 수 있는 UI입니다. 스켈레톤, 스피너, 대표 이미지 등 의미 있는 로딩 UI를 설계하는 것이 UX에 좋습니다.

데이터 패칭 패턴 예시

순차적 데이터 패칭(Sequential)

중첩된 컴포넌트가 각자 데이터를 패칭하면, 요청이 순차적으로 일어나 전체 응답 시간이 길어질 수 있습니다. 하지만 한 fetch가 다른 fetch의 결과에 의존하는 경우에는 이 패턴이 필요할 수 있습니다.

// app/artist/[username]/page.tsx
export default async function Page({ params }: { params: Promise<{ username: string }> }) {
  const { username } = await params
  const artist = await getArtist(username)
  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <Playlists artistID={artist.id} />
      </Suspense>
    </>
  )
}

async function Playlists({ artistID }: { artistID: string }) {
  const playlists = await getArtistPlaylists(artistID)
  return (
    <ul>
      {playlists.map((playlist) => (
        <li key={playlist.id}>{playlist.name}</li>
      ))}
    </ul>
  )
}

병렬 데이터 패칭(Parallel)

여러 데이터 요청을 동시에 시작하면 전체 응답 속도를 높일 수 있습니다. Promise.all을 활용하면 여러 fetch를 병렬로 처리할 수 있습니다.

// app/artist/[username]/page.tsx
import Albums from './albums'

async function getArtist(username: string) {
  const res = await fetch(`https://api.example.com/artist/${username}`)
  return res.json()
}

async function getAlbums(username: string) {
  const res = await fetch(`https://api.example.com/artist/${username}/albums`)
  return res.json()
}

export default async function Page({ params }: { params: Promise<{ username: string }> }) {
  const { username } = await params
  const artistData = getArtist(username)
  const albumsData = getAlbums(username)
  const [artist, albums] = await Promise.all([artistData, albumsData])
  return (
    <>
      <h1>{artist.name}</h1>
      <Albums list={albums} />
    </>
  )
}

참고: Promise.all에서 하나의 요청이라도 실패하면 전체가 실패합니다. 이럴 때는 Promise.allSettled를 사용할 수 있습니다.

데이터 프리로딩(Preloading)

데이터 프리로딩은 실제로 필요한 fetch보다 미리 데이터를 요청해두는 패턴입니다. 예를 들어, preload()를 먼저 호출해두면, 실제 컴포넌트가 렌더링될 때 이미 데이터가 준비되어 있을 수 있습니다.

// app/item/[id]/page.tsx
import { getItem } from '@/lib/data'

export default async function Page({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params
  preload(id)
  const isAvailable = await checkIsAvailable()
  return isAvailable ? <Item id={id} /> : null
}

export const preload = (id: string) => {
  void getItem(id)
}
export async function Item({ id }: { id: string }) {
  const result = await getItem(id)
  // ...
}

React의 cache 함수와 server-only 패키지를 활용하면 서버에서만 실행되는 캐시 유틸리티를 만들 수 있습니다.

// utils/get-item.ts
import { cache } from 'react'
import 'server-only'
import { getItem } from '@/lib/data'

export const preload = (id: string) => {
  void getItem(id)
}

export const getItem = cache(async (id: string) => {
  // ...
})

API Reference

이 페이지에서 언급된 주요 API는 아래에서 더 자세히 확인할 수 있습니다.


출처: https://nextjs.org/docs/app/getting-started/fetching-data

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유