[Next.JS] 데이터 패칭, 캐싱, 재검증

Simon·2024년 5월 31일
0
post-thumbnail

Data Fetching, Caching, and Revalidating

데이터 가져오기(fetching)는 모든 애플리케이션의 핵심 부분이다. React 및 Next.js에서 데이터를 가져오고, 캐시하고, 재검증하는 방법을 알아본다.

데이터를 가져올 수 있는 방법에는 네 가지가 있다.

  1. 서버에서 fetch 사용
  2. 서버에서 서드파티 라이브러리 사용
  3. 클라이언트에서 라우트(route) 핸들러 이용
  4. 클라이언트에서 서드파티 라이브러리 사용

서버에서 fetch를 사용하여 데이터 가져오기

Next.js는 기본 fetch 웹 API를 확장하여 서버의 각 fetch 요청에 대한 캐싱 및 재검증 동작을 구성할 수 있도록 한다. React는 React 컴포넌트 트리를 렌더링하는 동안 fetch 요청을 자동으로 메모하기 위해 fetch를 확장한다.

서버 컴포넌트, 라우트 핸들러, 서버 액션에서 async/await와 함께 fetch를 사용할 수 있다.

예시 코드
서버 컴포넌트인 Page 컴포넌트에 async를 사용하고 비동기 함수인 getData 함수를 호출하여 데이터를 받아오는 코드

async function getData() {
  const res = await fetch('https://api.example.com/...')
  // The return value is *not* serialized
  // You can return Date, Map, Set, etc.
 
  if (!res.ok) {
    // This will activate the closest `error.js` Error Boundary
    throw new Error('Failed to fetch data')
  }
 
  return res.json()
}
 
export default async function Page() {
  const data = await getData()
 
  return <main></main>
}

알아두면 좋은 것

  • Next.js는 cookies 및 headers 같은 서버 컴포넌트에서 데이터를 가져올 때 필요할 수 있는 유용한 함수를 제공한다. 이로 인해 요청(request) 시간 정보에 의존하므로 경로가 동적으로 렌더링된다.
  • 라우트 핸들러에서 라우트 핸들러가 React 컴포넌트 트리의 일부가 아니기 때문에 가져오기 요청이 memoized 되지 않는다.
  • 서버 액션에서는 가져오기 요청이 캐시되지 않는다. (defaults cache: no-store)
  • TypeScript가 포함된 서버 컴포넌트에서 async/await를 사용하려면 TypeScript 5.1.3 이상 및 @types/react 18.2.8 이상을 사용해야 한다.

데이터 캐싱

캐싱은 데이터를 저장하므로 요청이 있을 때마다 데이터 소스에서 다시 가져올 필요가 없다.

기본적으로 Next.js는 fetch의 반환 값을 서버의 데이터 캐시에 자동으로 캐시한다. 이는 빌드 시 또는 요청 시 데이터를 가져오고, 캐시하고, 각 데이터 요청에서 재사용할 수 있음을 의미한다.

// 'force-cache' is the default, and can be omitted
fetch('https://...', { cache: 'force-cache' })

그러나 예외가 있다. 다음과 같은 경우 fetch 요청이 캐시되지 않는다.

  • 서버 액션 내에서 사용
  • POST 메서드를 사용하는 라우트 핸들러 내에서 사용

데이터 재검증

재검증은 데이터 캐시를 제거하고 최신 데이터를 가져오는 프로세스다. 데이터가 변경되어 최신 정보를 표시하려는 경우에 유용하다.

캐시된 데이터는 다음 두 가지 방법으로 재검증 할 수 있습니다.

  • 시간 기반 재검증: 일정 시간이 지나면 데이터를 자동으로 재검증 한다.
  • 요구 재검증: 이벤트(예: form 제출)를 기반으로 데이터를 수동으로 재검증한다. 요구 재검증에서는 태그(tag) 기반 또는 경로(path) 기반 접근 방식을 사용하여 데이터 그룹을 한 번에 재검증할 수 있다. 이는 최신 데이터를 최대한 빨리 표시하려는 경우에 유용하다.

시간 기반 재검증(Time-based Revalidation)

일정 간격으로 데이터를 재검증하려면 fetch의 next.validate 옵션을 사용하여 리소스의 캐시 수명(초)을 설정할 수 있다.

fetch('https://...', { next: { revalidate: 3600 } })

정적으로 렌더링된 경로에 여러 개의 fetch 요청이 있고 각각의 재검증 빈도가 다른 경우. 모든 요청에 가장 낮은 시간이 사용된다. 동적으로 렌더링된 경로(route)의 경우 각 가져오기 요청은 독립적으로 재검증 된다.

요구 재검증(On-demand Revalidation)
데이터는 서버 액션이나 라우트 핸들러 내부의 revalidatePath 또는 revalidateTag를 통해 재검증 될 수 있다.

Next.js에는 경로 전반에 걸쳐 가져오기 요청을 무효화하기 위한 캐시 태깅(cache tagging) 시스템이 있다.

  1. fetch를 사용할 때 하나 이상의 태그로 캐시 항목에 태그를 지정할 수 있는 옵션이 있다.
  2. 그런 다음 revalidateTag를 호출하여 해당 태그와 연결된 모든 항목의 유효성을 재검증할 수 있다.

예를 들어, 다음 fetch 요청은 캐시 태그 컬렉션(the cache tag collection)을 추가한다.

export default async function Page() {
  const res = await fetch('https://...', { next: { tags: ['collection'] } })
  const data = await res.json()
  // ...
}

그런 다음 서버 액션에서 revalidateTag를 호출하여 collection 태그가 지정된 fetch 호출을 다시 검증할 수 있다.

'use server'
 
import { revalidateTag } from 'next/cache'
 
export default async function action() {
  revalidateTag('collection')
}

오류 처리 및 재검증

데이터 재검증을 시도하는 동안 오류가 발생하면 마지막으로 성공적으로 생성된 데이터가 캐시에서 계속 제공된다. 다음 후속 요청에서 Next.js는 데이터 재검증을 다시 시도한다.

데이터 캐싱 선택 해제

다음과 같은 경우 fetch 요청이 캐시되지 않는다.

  • 캐시: 'no-store'가 fetch 요청에 추가
  • revalidate: 0 옵션이 개별 fetch 요청에 추가
  • fetch 요청이 POST 메서드를 사용하는 라우터 핸들러 내부에 있을 경우
  • fetch 요청이 헤더나 쿠키를 사용한 후에 올 경우
  • const Dynamic = 'force-dynamic' 경로 세그먼트 옵션이 사용된 경우
  • fetchCache 경로 세그먼트 옵션은 기본적으로 캐시를 건너뛰도록 구성
  • fetch 요청은 Authorization 또는 Cookie 헤더를 사용

개별 fetch 요청
개별 fetch 요청에 대한 캐싱을 선택 해제하려면 fetch의 캐시 옵션을 'no-store'로 설정하면 된다. 그러면 요청이 있을 때마다 데이터를 동적으로 가져온다.

fetch('https://...', { cache: 'no-store' })

다중 fetch 요청
경로 세그먼트(예: 레이아웃 또는 페이지)에 여러 개의 가져오기 요청이 있는 경우 세그먼트 구성 옵션 Segment Config Options을 사용하여 세그먼트에 있는 모든 데이터 요청의 캐싱 동작을 구성할 수 있다.

그러나 각 가져오기 요청의 캐싱 동작을 개별적으로 구성하는 것이 좋다. 이를 통해 캐싱 동작을 보다 세부적으로 제어할 수 있다.

서드파티 라이브러리를 사용하여 서버에서 데이터 가져오기

fetch를 지원하거나 expose 하지 않는 타사 라이브러리(예: 데이터베이스, CMS 또는 ORM 클라이언트)를 사용하는 경우 Route Segment Config 옵션과 React의 캐시 기능을 사용하여 해당 요청의 캐싱 및 재검증 동작을 구성할 수 있다.

데이터가 캐시되는지 여부는 경로 세그먼트가 정적으로 렌더링되는지 아니면 동적으로 렌더링되는지에 따라 달라진다. 세그먼트가 정적(기본값)인 경우 요청 출력은 경로 세그먼트의 일부로 캐시되고 재검증된다.세그먼트가 동적인 경우 요청 출력은 캐시되지 않으며 세그먼트가 렌더링될 때 모든 요청에서 다시 가져온다.

예시

아래 예에서:

- React 캐시 함수는 데이터 요청을 memoize하는데 사용된다.

  • 재검증 옵션은 레이아웃 및 페이지 세그먼트에서 3600으로 설정된다. 즉, 데이터가 캐시되고 최대 매시간 재검증된다.
import { cache } from 'react'
 
export const getItem = cache(async (id) => {
  const item = await db.item.findUnique({ id })
  return item
})

getItem 함수가 두 번 호출되더라도 데이터베이스에 대한 쿼리는 한 번만 수행된다.

app/item/[id]/layout.js

import { getItem } from '@/utils/get-item'
 
export const revalidate = 3600 // revalidate the data at most every hour
 
export default async function Layout({ params: { id } }) {
  const item = await getItem(id)
  // ...
}

app/item/[id]/page.js

import { getItem } from '@/utils/get-item'
 
export const revalidate = 3600 // revalidate the data at most every hour
 
export default async function Page({ params: { id } }) {
  const item = await getItem(id)
  // ...
}

라우트 핸들러를 사용하여 클라이언트에서 데이터 가져오기

클라이언트 컴포넌트에서 데이터를 가져와야 하는 경우 클라이언트에서 Route Handler를 호출할 수 있다. 라우트 핸들러는 서버에서 실행되고 데이터를 클라이언트에 반환한다. 이는 API 토큰과 같은 민감한 정보를 클라이언트에 노출하고 싶지 않을 때 유용하다.

서버 컴포넌트 및 라우트 핸들러

서버 컴포넌트는 서버에서 렌더링되므로 데이터를 가져오기 위해 서버 컴포넌트에서 라우트 핸들러를 호출할 필요가 없다. 대신 서버 컴포넌트 내부에서 직접 데이터를 가져올 수 있습니다.

서드파티 라이브러리를 사용하여 클라이언트에서 데이터 가져오기

SWR 또는 TanStack Query와 같은 서드파티 라이브러리를 사용하여 클라이언트에서 데이터를 가져올 수도 있다. 이러한 라이브러리는 요청 메모, 캐싱, 재검증 및 데이터 변형을 위한 자체 API를 제공한다.

Next.js Data Fetching, Caching, and Revalidating 공식문서

profile
포기란 없습니다.

0개의 댓글