가능하다면 서버 사이드에서 데이터를 가져오는 것을 추천합니다. 데이터를 가져오는 작업과 렌더링 작업을 같은 환경에서 할 수 있고 엑세스 토큰과 API 키 값 같은 민감한 정보를 노출하지 않아도 됩니다. 서버 컴포넌트, Route Handlers, 서버 Actions를 사용해서 데이터를 가져올 수 있습니다.
스트리밍과 Suspense는 리액트의 기능인데 클아이언트에 점진적으로 UI요소를 스트리밍할 수 있습니다. 데이터가 필요하지 않은 부분은 즉시 렌더링하고, 데이터 로딩이 필요한 부분에 있어서는 로딩 상태를 보여줄 수 있습니다. 이로 인해 사용자는 모든 페이지를 로딩하기 전에 부분적으로 페이지 컨텐츠를 이용할 수 있습니다.
순차적으로 이뤄져야하는 api 호출이 있을 때 사용합니다. 1번 작업이 끝나야 2번 작업을 진행할 수 있는 경우가 있겠습니다. 하위 컴포넌트를 Suspense로 감싸서 상위 컴포넌트에서 데이터 로딩을 완료했을 때 하위 컴포넌트의 데이터 로딩이 시작되도록 구성할 수 있습니다.
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>
</>
)
}
모든 fetch 작업이 끝날 때까지 사용자는 화면을 볼 수 없게됩니다. Suspense Boundary를 추가해서 점진적 렌더링을 하도록 개선할 수 있습니다.
상위 컴포넌트에서 preload를 호출해서 하위 컴포넌트에서 필요로 하는 데이터를 더 빠른 타이밍에 fetch하기 시작합니다.
import { getItem } from '@/utils/get-item'
export const preload = (id: string) => {
// void evaluates the given expression and returns undefined
// https://developer.mozilla.org/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 함수로 api 호출을 캐싱하고 server-only로 해당 데이터 로딩 작업이 서버 사이드에서만 실행되는 것을 보장할 수 있습니다. 이를 preload 패턴과 조합하면 더 빠른 타이밍에 데이터 로딩 작업을 시작하도록 할 수 있습니다.
import { cache } from 'react'
import 'server-only'
export const preload = (id: string) => {
void getItem(id)
}
export const getItem = cache(async (id: string) => {
// ...
})