[Next.js] 데이터 가져오기 패턴

파이리·2023년 8월 2일
0

Next.js

목록 보기
17/18

React와 Next.js에서 데이터를 가져오는 데 권장되는 몇 가지 패턴과 모법 사례가 있습니다. 이 페이지에서는 가장 일반적인 패턴 몇 가지와 그 사용법을 살펴봅니다.


서버에서 데이터 가져오기

가능하면 서버에서 데이터를 가져오는 것이 좋습니다. 이렇게 하면 다음과 같이 할 수 있습니다.

  • 백엔드 데이터 리소스 (ex: 데이터베이스)에 직접 엑세스할 수 있습니다.

  • 엑세스 토큰 및 API 키와 같은 민감한 정보가 클라이언트에 노출되는 것을 방지하여 애플리케이션을 더욱 안전하게 보호할 수 있습니다.

  • 동일한 환경에서 데이터 가져오기 및 랜더링. 이렇게 하면 클라이언트와 서버 간의 주고받는 통신은 물론 클라이언트의 메인 스레드 작업도 줄어듭니다.

  • 클라이언트에서 여려 번의 개별 요청 대신 한 번의 왕복으로 여러 데이터 가져오기를 수행합니다.

  • 클라이언트 - 서버 워터풀 감소

  • 지역에 따라 데이터 가져오기를 데이터 소스와 더 가까운 곳에서 수행하여 지연 시간을 줄이고 성능을 개선할 수 있습니다.


필요한 곳에서 데이터 가져오기

트리의 여러 컴포넌트에서 동일한 데이터를 사용해야 하는 경우, 데이터를 전역적으로 가져오거나 컴포넌트 간에 프로퍼티를 전달할 필요가 없습니다. 대신 동일한 데이터를 여러 번 요청할 때 성능에 미치는 영향을 걱정하지 않고 데이터가 필요한 곳에서 fetch 또는 React 캐시를 사용할 수 있습니다.

이는 fetch 요청이 자동으로 메모화되기 때문에 가능합니다.

Good to Know

부모 레이아웃과 자식 레이아웃 간에 데이터를 전달할 수 없으므로 레이아웃에도 적용됩니다.


스트리밍

스트리밍과 서스펜스는 UI의 랜더링된 단위를 점진적으로 랜더링하고 클라이언트에 점진적으로 스트리밍할 수 있는 React 기능입니다.

서버 컴포넌트와 중첩 레이아웃을 사용하면 특별히 데이터가 필요하지 않은 페이지의 일부를 즉시 랜더링하고 데이터를 불러오은 페이지의 일부에 대해 로딩 상태를 표시할 수 있습니다. 즉, 사용자는 페이지 전체가 로드될 때까지 기다렸다가 페이지와 상호 작용을 시작할 수 있습니다.


병렬 및 순차 데이터 불러오기

React 컴포넌트 내에서 데이터를 불러올 때 두 가지 데이터 불러오기 패턴(병렬과 순차)을 알고 있어야 합니다.

  • 순차적 데이터 가져오기를 사용하면 라우트의 요청이 서로 종속되므로 워터풀이 발생합니다. 한 fetch가 다른 fetch 결과에 의존하거나 리소스를 절약하기 위해 다음 fetch 전에 조건이 충족되기를 원하기 때문에 이 패턴을 원하는 경우가 있을 수 있습니다. 그러나 이 동작은 의도치 않게 로딩 시간이 길어질 수 있습니다.

  • 병렬 데이터 가져오기를 사용하면 한 라우트의 요청이 동시에 시작되어 데이터를 로드합니다. 따라서 클라이언트-서버 워터폴과 데이터 로딩에 걸리는 총 시간이 줄어듭니다.

순차적 데이터 가져오기

중첩된 컴포넌트가 있고 각 컴포넌트가 자체 데이터를 가져오는 경우, 데이터 요청이 서로 다른 경우 fetch가 순차적으로 수행됩니다. (동일한 fetch 요청은 자동으로 메모화되므로 적용되지 않음)

예를 들어 Playllists 컴포넌트는 Artist 컴포넌트가 fetch를 완료한 후에야 fetch를 시작하는데, 이는 PlaylistsartistID 프로퍼티에 의존하기 때문입니다.

// ...
 
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에 의해 차단되는 것을 방지할 수 있습니다. 사용자는 차단되지 않은 페이지 부분과 상호작용할 수 있습니다.

병렬 데이터 가져오기

데이터를 병렬로 가져오려면 데이터를 사용하는 컴포넌트 외부에서 요청을 정의한 다음 컴포넌트 내부에서 호출하여 요청을 신속하게 시작할 수 있습니다. 이렇게 하면 두 요청을 동시에 시작하여 시간을 절약할 수 있지만 두 프로미스가 모두 해결될 때까지 사용자는 랜더링 결과를 볼 수 없습니다.

아래 예시에서 getArtistgetArtistAlbums 함수를 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
}

React cache, server-only 및 프리로드 패턴 사용

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) => {
  // ...
})

이 접근 방식을 사용하면 데이터를 가져오고, 응답을 캐시하고, 이 데이터 가져오기가 서버에서만 발생하도록 보장할 수 있습니다.

profile
프론트엔드 개발자

0개의 댓글