[번역] 데이터 Fetching

Next.js
2023-09-21

원글 : https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating

데이터 fetching은 모든 애플리케이션의 핵심 부분이다.

이 페이지에서는 리액트 및 Next.js에서 데이터를 fetch하고, 캐시하고, 재검증하는 방법에 대해 설명한다.

데이터를 fetch하는 방법은 4가지가 있다.

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

서버에서 fetch를 이용한 데이터 Fetching

Next.js는 네이티브 fetch 웹 API를 확장하여 서버의 각 fetch 요청에 대한 캐시 및 재검증 동작을 구성할 수 있다. 리액트는 fetch를 확장하여 리액트 컴포넌트 트리를 렌더링하는 동안 fetch 요청을 자동으로 기록한다.

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

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> }
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는 cookiesheader와 같은 서버 컴포넌트에서 데이터를 검색할 때 필요할 수 있는 유용한 함수를 제공한다. 이러한 함수는 요청 시간 정보에 따라 다르므로 라우트가 동적으로 렌더링된다.
  • 라우터 핸들러는 리액트 컴포넌트 트리의 일부가 아니므로 fetch 요청을 기록하지 않는다.
  • 서버 작업은 fetch 요청을 캐시하지 않는다(기본 cache: no-store).

데이터 캐싱

캐시는 데이터를 저장하므로 요청 당 데이터 소스에서 다시 가져올 필요가 없다.

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

// 'force-cache'는 기본 값이며, 생략 할 수 있습니다. fetch('https://...', { cache: 'force-cache' })
// 'force-cache'는 기본 값이며, 생략 할 수 있습니다. fetch('https://...', { cache: 'force-cache' })

아래와 같은 예외에는 fetch 요청이 캐시되지 않는다.

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

데이터 캐시란 무엇입니까?
데이터 캐시는 영구 HTTP 캐시이다. 플랫폼에 따라 캐시를 자동으로 확장하여 여러 리전간에 공유할 수 있다.

데이터 재검증

재검증은 데이터 캐시를 지우고 최신 데이터를 다시 검색하는 프로세스이다. 이는 데이터가 변경되고 최신 정보를 확실하게 표시하려는 경우에 유용하다.

캐시된 데이터는 다음 두 가지 방법으로 다시 확인할 수 있다.

시간 기반 재검증: 일정 시간이 경과하면 데이터를 자동으로 재검증한다. 이것은 자주 변경되지 않으며 신선도가 그다지 중요하지 않은 데이터에 유용하다.

요구 기반 재검증: 이벤트(예: 양식 제출)를 기반으로 데이터를 수동으로 다시 확인한다. 요구 기반 재검증을 사용하면 태그 기반 또는 경로 기반 접근 방식을 사용하여 데이터 그룹을 한 번에 다시 확인할 수 있다. 이는 최신 데이터가 가능한 한 빨리 표시되도록 하려는 경우(예: 헤드리스 CMS 콘텐츠가 업데이트된 경우)에 유용하다.

시간 기반 재검증

시간 간격으로 데이터를 다시 확인하려면 fetchnext.revalidate 옵션을 사용하여 리소스의 캐시 수명(초)을 설정할 수 있다.

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

또는 라우트 세그먼트의 모든 fetch 요청을 다시 확인하려면 세그먼트 구성 옵션을 사용할 수 있다.

// layout.js | page.js export const revalidate = 3600 // revalidate at most every hour
// layout.js | page.js export const revalidate = 3600 // revalidate at most every hour

정적으로 렌더링된 라우트에 여러 개의 fetch 요청이 있고 각각에 대해 서로 다른 재검증 빈도가 있는 경우 가장 짧은 시간이 모든 요청에 ​​사용된다. 동적으로 렌더링된 경로의 경우 각 fetch 요청은 개별적으로 다시 확인된다.

온디맨드 재검증

데이터는 서버 액션이나 라우터 핸들러의 경로(revalidatePath) 또는 캐시 태그(revalidateTag)를 통해 온디맨드로 다시 확인할 수 있다.

Next.js에는 라우트간에 fetch 요청을 비활성화하는 캐시 태그 지정 시스템이 있다.

  1. fetch를 사용하는 경우 하나 이상의 태그를 사용하여 캐시 항목에 태그를 지정하는 옵션이 있다.
  2. 그런 다음 revalidateTag를 호출하여 태그와 연관된 모든 항목을 다시 확인할 수 있다.

예를 들어, 다음 fetch 요청은 캐시 태그 collection을 추가합니다.

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

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

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

오류 처리 및 재검증

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

데이터 캐시 선택 해제

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

  • fetch 요청에 cache: 'no-store'가 추가됐을 때
  • 개별 fetch 요청에 revalidate: 0 옵션이 추가됐을 때
  • fetch 요청이 POST 메소드를 사용하는 라우터 핸들러에 있을 때
  • fetch 요청이 headers 또는 cookies를 사용한 후에
  • const dynamic = 'force-dynamic' 라우트 세그먼트 옵션이 사용될 때
  • fetchCache 라우트 세그먼트 옵션이 기본적으로 캐시를 건너뛰도록 구성될 때
  • fetch 요청에 Authorization 또는 Cookie 헤더가 사용되고 컴포넌트 트리에서 캐시되지 않은 요청이 있을 때

개별 fetch 요청

개별 fetch 요청의 캐시를 선택 해제하려면 fetch 캐시 옵션을 'no-store'로 설정할 수 있다. 이렇게 하면 요청 당 데이터가 동적으로 검색된다.

// layout.js | page.js fetch('https://...', { cache: 'no-store' })
// layout.js | page.js fetch('https://...', { cache: 'no-store' })

fetch API 참조에서 사용 가능한 모든 캐시 옵션을 검토하시오.

여러 fetch 요청

라우트 세그먼트(예: 레이아웃 또는 페이지)에 여러 fetch 요청이 있는 경우, 세그먼트 구성 옵션을 사용하여 세그먼트의 모든 데이터 fetch에 대한 캐시 동작을 설정할 수 있다.

그러나 각 fetch 요청에 대한 캐시 동작을 개별적으로 설정하는 것이 좋다. 이렇게 하면 캐시 동작을 보다 세밀하게 제어할 수 좋다.


타사 라이브러리를 사용하여 서버에서 데이터 검색

fetch를 지원하거나 게시하지 않은 써드파티 라이브러리(예: 데이터베이스, CMS, ORM 클라이언트 등)를 사용하는 경우 라우트 세그먼트 구성 옵션과 리액트 cache 함수를 사용하여 해당 요청의 캐시 및 재확인 동작을 구성할 수 있다.

데이터가 캐시되는지 여부는 라우트 세그먼트가 정적으로 렌더링되는지 동적으로 렌더링되는지에 따라 다르다. 세그먼트가 정적(기본값)이면 요청의 출력이 루트 세그먼트의 일부로 캐시되고 다시 확인된다. 세그먼트가 동적이면 요청의 출력은 캐시되지 않고 세그먼트가 렌더링 될 때마다 요청마다 다시 가져온다.

실험적인 stable_cache API를 사용할 수도 있다.

예시

  • 리액트 cache 함수를 사용하여 데이터 요청을 기록한다.
  • 레이아웃 세그먼트와 페이지 세그먼트 재검증 옵션은 3600으로 설정됩니다. 즉, 데이터는 최대 1시간마다 캐시되고 다시 확인된다.
// app/utils.ts import { cache } from 'react' export const getItem = cache(async (id: string) => { const item = await db.item.findUnique({ id }) return item })
// app/utils.ts import { cache } from 'react' export const getItem = cache(async (id: string) => { const item = await db.item.findUnique({ id }) return item })

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

//app/item/[id]/layout.tsx import { getItem } from '@/utils/get-item' export const revalidate = 3600 // revalidate the data at most every hour export default async function Layout({ params: { id }, }: { params: { id: string } }) { const item = await getItem(id) // ... }
//app/item/[id]/layout.tsx import { getItem } from '@/utils/get-item' export const revalidate = 3600 // revalidate the data at most every hour export default async function Layout({ params: { id }, }: { params: { id: string } }) { const item = await getItem(id) // ... }
// app/item/[id]/page.tsx import { getItem } from '@/utils/get-item' export const revalidate = 3600 // revalidate the data at most every hour export default async function Page({ params: { id }, }: { params: { id: string } }) { const item = await getItem(id) // ... }
// app/item/[id]/page.tsx import { getItem } from '@/utils/get-item' export const revalidate = 3600 // revalidate the data at most every hour export default async function Page({ params: { id }, }: { params: { id: string } }) { const item = await getItem(id) // ... }

라우트 핸들러를 사용하여 클라이언트에서 데이터 검색

클라이언트 컴포넌트에서 데이터를 검색해야 하는 경우 클라이언트에서 루트 처리기를 호출할 수 있다. 라우트 핸들러는 서버에서 실행되고 데이터를 클라이언트에 반환한다. 이것은 API 토큰 등의 기밀 정보를 클라이언트에 공개하고 싶지 않은 경우에 유용하다.

서버 컴포넌트 및 루트 처리기
서버 컴포넌트는 서버에서 렌더링되므로 데이터를 검색하기 위해 서버 컴포넌트에서 라우트 핸들러를 호출할 필요가 없다. 대신 서버 컴포넌트 내에서 직접 데이터를 검색할 수 있다.


써드 파티 라이브러리를 사용하여 클라이언트에서 데이터 검색

SWRTanStack Query와 같은 타사 라이브러리를 사용하여 클라이언트에서 데이터를 검색할 수도 있다. 이러한 라이브러리는 요청을 메모하고, 캐시, 재검증 및 데이터를 변경할 수 있는 고유한 API를 제공한다.

향후 API:
use 는 함수에 의해 반환된 promise 를 받아들이고 처리하는 리액트 함수이다. 사용 중에 fetch를 래핑하는 것은 현재 클라이언트 컴포넌트에서 권장되지 않으며 여러 재렌더링이 트리거 될 수 있다. use에 대한 자세한 내용은 리액트 문서를 참조하십시오.

관련 포스트

post thumbnail

[번역] Next.js 14

Next.js 14는 다음과 같은 가장 중점으로 둔 릴리스이다. - Turbopack: App & Pages Router에서 5,000번의 테스트 통과 - 로컬 서버 시작이 53%

2024-03-12
post thumbnail

[번역] 캐싱

Next.js는 렌더링 작업과 데이터 요청을 캐싱하여 어플리케이션의 성능을 향상시키고 비용을 감소시킨다. 기본적으로 Next.js는 성능을 향상시키고 비용을 줄이기 위해 가능한 한

2024-03-12
post thumbnail

[번역] 구성 패턴

리액트 애플리케이션을 빌드할 때, 애플리케이션의 어느 부분을 서버 또는 클라이언트에서 렌더링할지 고려해야 한다. 이 글은 서버 및 클라이언트 컴포넌트를 사용할 때 권장되는 몇 가지

2024-03-11
post thumbnail

[번역] 클라이언트 컴포넌트

클라이언트 컴포넌트는 요청 시 클라이언트에서 렌더링할 수 있는 인터랙티브한 UI를 만들 수 있게 해준다. Next.js에서 클라이언트 렌더링은 opt-in으로, 리액트가 클라이언트

2024-03-11

도로모의 기술 블로그

# Contact : jyw966@naver.com

Copyright © doromo. All Rights Reserved.