React와 Next.js에서 데이터를 가져오는 데 권장되는 몇 가지 패턴과 모법 사례가 있습니다. 이 페이지에서는 가장 일반적인 패턴 몇 가지와 그 사용법을 살펴봅니다.
가능하면 서버에서 데이터를 가져오는 것이 좋습니다. 이렇게 하면 다음과 같이 할 수 있습니다.
백엔드 데이터 리소스 (ex: 데이터베이스)에 직접 엑세스할 수 있습니다.
엑세스 토큰 및 API 키와 같은 민감한 정보가 클라이언트에 노출되는 것을 방지하여 애플리케이션을 더욱 안전하게 보호할 수 있습니다.
동일한 환경에서 데이터 가져오기 및 랜더링. 이렇게 하면 클라이언트와 서버 간의 주고받는 통신은 물론 클라이언트의 메인 스레드 작업도 줄어듭니다.
클라이언트에서 여려 번의 개별 요청 대신 한 번의 왕복으로 여러 데이터 가져오기를 수행합니다.
클라이언트 - 서버 워터풀 감소
지역에 따라 데이터 가져오기를 데이터 소스와 더 가까운 곳에서 수행하여 지연 시간을 줄이고 성능을 개선할 수 있습니다.
트리의 여러 컴포넌트에서 동일한 데이터를 사용해야 하는 경우, 데이터를 전역적으로 가져오거나 컴포넌트 간에 프로퍼티를 전달할 필요가 없습니다. 대신 동일한 데이터를 여러 번 요청할 때 성능에 미치는 영향을 걱정하지 않고 데이터가 필요한 곳에서 fetch
또는 React 캐시를 사용할 수 있습니다.
이는 fetch
요청이 자동으로 메모화되기 때문에 가능합니다.
Good to Know
부모 레이아웃과 자식 레이아웃 간에 데이터를 전달할 수 없으므로 레이아웃에도 적용됩니다.
스트리밍과 서스펜스는 UI의 랜더링된 단위를 점진적으로 랜더링하고 클라이언트에 점진적으로 스트리밍할 수 있는 React 기능입니다.
서버 컴포넌트와 중첩 레이아웃을 사용하면 특별히 데이터가 필요하지 않은 페이지의 일부를 즉시 랜더링하고 데이터를 불러오은 페이지의 일부에 대해 로딩 상태를 표시할 수 있습니다. 즉, 사용자는 페이지 전체가 로드될 때까지 기다렸다가 페이지와 상호 작용을 시작할 수 있습니다.
React 컴포넌트 내에서 데이터를 불러올 때 두 가지 데이터 불러오기 패턴(병렬과 순차)을 알고 있어야 합니다.
순차적 데이터 가져오기를 사용하면 라우트의 요청이 서로 종속되므로 워터풀이 발생합니다. 한 fetch
가 다른 fetch
결과에 의존하거나 리소스를 절약하기 위해 다음 fetch
전에 조건이 충족되기를 원하기 때문에 이 패턴을 원하는 경우가 있을 수 있습니다. 그러나 이 동작은 의도치 않게 로딩 시간이 길어질 수 있습니다.
병렬 데이터 가져오기를 사용하면 한 라우트의 요청이 동시에 시작되어 데이터를 로드합니다. 따라서 클라이언트-서버 워터폴과 데이터 로딩에 걸리는 총 시간이 줄어듭니다.
중첩된 컴포넌트가 있고 각 컴포넌트가 자체 데이터를 가져오는 경우, 데이터 요청이 서로 다른 경우 fetch
가 순차적으로 수행됩니다. (동일한 fetch
요청은 자동으로 메모화되므로 적용되지 않음)
예를 들어 Playllists
컴포넌트는 Artist
컴포넌트가 fetch
를 완료한 후에야 fetch
를 시작하는데, 이는 Playlists
가 artistID
프로퍼티에 의존하기 때문입니다.
// ...
async function Playlists({ artistID }: { artistID: string }) {
// Wait for the playlists
const playlists = await getArtistPlaylists(artistID)
return (
<ul>
{playlists.map((playlist) => (
<li key={playlist.id}>{playlist.name}</li>
))}
</ul>
)
}
export default async function Page({
params: { username },
}: {
params: { username: string }
}) {
// Wait for the artist
const artist = await getArtist(username)
return (
<>
<h1>{artist.name}</h1>
<Suspense fallback={<div>Loading...</div>}>
<Playlists artistID={artist.id} />
</Suspense>
</>
)
}
이런 경우, loading.js
또는 React의 <Suspense>
를 사용하여 결과에서 React가 스트리밍되는 동안 즉각적인 로딩 상태를 표시할 수 있습니다.
이렇게 하면 전체 라우트가 fetch
에 의해 차단되는 것을 방지할 수 있습니다. 사용자는 차단되지 않은 페이지 부분과 상호작용할 수 있습니다.
데이터를 병렬로 가져오려면 데이터를 사용하는 컴포넌트 외부에서 요청을 정의한 다음 컴포넌트 내부에서 호출하여 요청을 신속하게 시작할 수 있습니다. 이렇게 하면 두 요청을 동시에 시작하여 시간을 절약할 수 있지만 두 프로미스가 모두 해결될 때까지 사용자는 랜더링 결과를 볼 수 없습니다.
아래 예시에서 getArtist
와 getArtistAlbums
함수를 Page
컴포넌트 외부에 정의한 다음 컴포넌트 내부에서 호출하고 두 프로미스가 모두 해결될 때까지 기다립니다.
import Albums from './albums'
async function getArtist(username: string) {
const res = await fetch(`https://api.example.com/artist/${username}`)
return res.json()
}
async function getArtistAlbums(username: string) {
const res = await fetch(`https://api.example.com/artist/${username}/albums`)
return res.json()
}
export default async function Page({
params: { username },
}: {
params: { username: string }
}) {
// Initiate both requests in parallel
const artistData = getArtist(username)
const albumsData = getArtistAlbums(username)
// Wait for the promises to resolve
const [artist, albums] = await Promise.all([artistData, albumsData])
return (
<>
<h1>{artist.name}</h1>
<Albums list={albums}></Albums>
</>
)
}
사용자 경험을 개선하기 위해서 Suspense
바운더리를 추가하여 랜더링 작업을 분하라고 결과의 일부를 가능한 한 빨리 표시할 수 있습니다.
워터폴을 방지하는 방법으로 프로로드 패턴이 있습니다. 선택적으로 preload
함수를 생성하여 병렬 데이터 가져오기를 더욱 최적화할 수 있습니다. preload
함수는 API가 아닌 패턴이기 때문에 어떤 함수명을 사용해도 무방합니다.
import { getItem } from '@/utils/get-item'
export const preload = (id: string) => {
// void evaluates the given expression and returns undefined
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void
void getItem(id)
}
export default async function Item({ id }: { id: string }) {
const result = await getItem(id)
// ...
}
import Item, { preload, checkIsAvailable } from '@/components/Item'
export default async function Page({
params: { id },
}: {
params: { id: string }
}) {
// starting loading item data
preload(id)
// perform another asynchronous task
const isAvailable = await checkIsAvailable()
return isAvailable ? <Item id={id} /> : null
}
cache
함수, preload
패턴 그리고 server-only
패키지를 결합하면 앱 전체에서 사용할 수 있는 데이터 가져오기 유틸리티를 만들 수 있습니다.
import { cache } from 'react'
import 'server-only'
export const preload = (id: string) => {
void getItem(id)
}
export const getItem = cache(async (id: string) => {
// ...
})
이 접근 방식을 사용하면 데이터를 가져오고, 응답을 캐시하고, 이 데이터 가져오기가 서버에서만 발생하도록 보장할 수 있습니다.