[Next 13] Data Fetching - Data Fetching, Caching 및 Revalidating

Jeongho·2023년 9월 6일
1
  • 데이터 가져오기는 모든 애플리케이션의 핵심 부분입니다. 이 페이지에서는 React 및 Next.js에서 데이터를 가져오고, 캐시하고, 재검증하는 방법을 설명합니다.
  • 데이터를 가져올 수 있는 방법에는 네 가지가 있습니다.
    • 서버에서 fetch를 사용하여
    • 서버에서 타사 라이브러리 사용하여
    • 클라이언트에서 route handler를 통해
    • 클라이언트에서 타사 라이브러리를 사용하여

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

  • Next.js는 기본 fetch 웹 API를 확장하여 서버의 각 fetch 요청에 대한 캐싱 및 재검증 동작을 구성할 수 있도록 합니다. React는 React 컴포넌트 트리를 렌더링하는 동안 fetch 요청을 자동으로 메모하기 위해 fetch를 확장합니다.
  • 서버 컴포넌트, route handler 및 서버 작업에서 async/await와 함께 fetch를 사용할 수 있습니다.
// app/page.tsx
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는 쿠키 및 헤더와 같은 서버 컴포넌트에서 데이터를 가져올 때 필요할 수 있는 유용한 기능을 제공합니다. 이로 인해 요청 시간 정보에 의존하므로 경로가 동적으로 렌더링됩니다.
    • route handler에서는 route handler가 React 컴포넌트 트리의 일부가 아니기 때문에 fetch 요청이 메모되지 않습니다.
    • TypeScript가 포함된 서버 컴포넌트에서 async/await를 사용하려면 TypeScript 5.1.3 이상 및 @types/react 18.2.8 이상을 사용해야 합니다.

데이터 캐싱

  • 캐싱은 데이터를 저장하므로 요청이 있을 때마다 데이터 소스에서 다시 가져올 필요가 없습니다.
  • 기본적으로 Next.js는 fetch의 반환 값을 서버의 데이터 캐시에 자동으로 캐시합니다. 이는 빌드 시 또는 요청 시 데이터를 가져오고, 캐시하고, 각 데이터 요청에서 재사용할 수 있음을 의미합니다.
// force-cache가 기본값이며 생략 가능
fetch('https://...', { cache: 'force-cache' })
  • POST 메서드를 사용하는 fetch 요청도 자동으로 캐시됩니다. POST 메서드를 사용하는 route handler 내부에 있지 않으면 캐시되지 않습니다.
  • 데이터 캐시란 무엇일까?
    • 데이터 캐시는 영구 HTTP 캐시입니다. 플랫폼에 따라 캐시는 자동으로 확장되고 여러 지역에서 공유될 수 있습니다.

데이터 재검증

  • 재검증은 데이터 캐시를 제거하고 최신 데이터를 다시 가져오는 프로세스입니다. 이는 데이터가 변경되어 최신 정보를 표시하려는 경우에 유용합니다.
  • 캐시된 데이터는 다음 두 가지 방법으로 유효성을 다시 검사할 수 있습니다.
    • 시간 기반 재검증: 일정 시간이 지나면 데이터를 자동으로 재검증합니다. 이는 자주 변경되지 않고 최신성이 중요하지 않은 데이터에 유용합니다.
    • 주문형 재검증: 이벤트(예: 양식 제출)를 기반으로 데이터를 수동으로 재검증합니다. 주문형 재검증에서는 태그 기반 또는 경로 기반 접근 방식을 사용하여 데이터 그룹을 한 번에 재검증할 수 있습니다. 이는 최신 데이터를 최대한 빨리 표시하려는 경우에 유용합니다(예: 헤드리스 CMS의 콘텐츠가 업데이트되는 경우).
  • 시간 기반 재검증
    • 일정 간격으로 데이터를 재검증하려면 fetch의 next.revalidate 옵션을 사용하여 리소스의 캐시 수명(초)을 설정할 수 있습니다.
fetch('https://...', { next: { revalidate: 3600 } })
  • 또는 경로 세그먼트의 모든 fetch 요청을 다시 검증하려면 세그먼트 구성 옵션을 사용할 수 있습니다.
// layout.js / page.js
export const revalidate = 3600 // revalidate at most every hour
  • 정적으로 렌더링된 경로에 여러 개의 fetch요청이 있고 각각의 재검증 빈도가 다른 경우. 모든 요청에 가장 낮은 시간이 사용됩니다. 동적으로 렌더링된 경로의 경우 각 fetch 요청은 독립적으로 재검증됩니다. (시간 기반 재검증)
  • 주문형 재검증
    • route handler 또는 서버 작업 내의 경로(revalidatePath) 또는 캐시 태그(revalidateTag)를 통해 요청 시 데이터의 유효성을 다시 검사할 수 있습니다.
    • Next.js에는 경로 전반에 걸쳐 fetch 요청을 무효화하기 위한 캐시 태깅 시스템이 있습니다.
      • fetch를 사용할 때 하나 이상의 태그로 캐시 항목에 태그를 지정할 수 있는 옵션이 있습니다.
      • 그런 다음 revalidateTag를 호출하여 해당 태그와 연결된 모든 항목의 유효성을 다시 검사할 수 있습니다.
    • 예를 들어 다음 fetch 요청은 캐시 태그 컬렉션을 추가합니다.
// app/page.tsx
export default async function Page() {
  const res = await fetch('https://...', { next: { tags: ['collection'] } })
  const data = await res.json()
  // ...
}
  • Route Handler를 사용하는 경우 Next.js 앱에서만 알려진 비밀 토큰을 생성해야 합니다. 이 비밀은 무단 재검증 시도를 방지하는 데 사용됩니다. 예를 들어 다음 URL 구조를 사용하여 (수동 또는 웹후크를 사용하여) 경로에 액세스할 수 있습니다.
https://<your-site.com>/api/revalidate?tag=collection&secret=<token>
// app/api/revaildate/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { revalidateTag } from 'next/cache'
 
// e.g a webhook to `your-website.com/api/revalidate?tag=collection&secret=<token>`
export async function POST(request: NextRequest) {
  const secret = request.nextUrl.searchParams.get('secret')
  const tag = request.nextUrl.searchParams.get('tag')
 
  if (secret !== process.env.MY_SECRET_TOKEN) {
    return NextResponse.json({ message: 'Invalid secret' }, { status: 401 })
  }
 
  if (!tag) {
    return NextResponse.json({ message: 'Missing tag param' }, { status: 400 })
  }
 
  revalidateTag(tag)
 
  return NextResponse.json({ revalidated: true, now: Date.now() })
}
  • 또는 revalidatePath를 사용하여 경로와 연결된 모든 데이터의 유효성을 다시 검사할 수 있습니다.
// app/api/revlidate/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { revalidatePath } from 'next/cache'
 
export async function POST(request: NextRequest) {
  const path = request.nextUrl.searchParams.get('path')
 
  if (!path) {
    return NextResponse.json({ message: 'Missing path param' }, { status: 400 })
  }
 
  revalidatePath(path)
 
  return NextResponse.json({ revalidated: true, now: Date.now() })
}
  • 에러 처리 및 재검증
    • 데이터 재검증을 시도하는 동안 오류가 발생하면 마지막으로 성공적으로 생성된 데이터가 캐시에서 계속 제공됩니다. 다음 후속 요청에서 Next.js는 데이터 재검증을 다시 시도합니다.

데이터 캐싱 선택 해제

  • 다음과 같은 경우 fetch요청이 캐시되지 않습니다.
    • 캐시: 'no-store'가 요청 fetch에 추가
    • revalidate: 0 옵션이 개별 fetch 요청에 추가
    • fetch 요청이 POST 메서드를 사용하는 라우터 핸들러 내부에 있습니다.
    • fetch 요청이 header나 cookies를 사용한 후에 발생합니다.
    • const Dynamic = 'force-dynamic' 경로 세그먼트 옵션이 사용됩니다.
    • fetchCache 경로 세그먼트 옵션은 기본적으로 캐시를 건너뛰도록 구성됩니다.
    • fetch 요청은 Authorization 또는 Cookie 헤더를 사용하며 컴포넌트 트리의 그 위에 캐시되지 않은 요청이 있습니다.
  • 개별 fetch 요청
    • 개별 fetch 요청에 대한 캐싱을 선택 해제하려면 fetch의 캐시 옵션을 'no-store'로 설정하면 됩니다. 그러면 요청이 있을 때마다 데이터를 동적으로 가져옵니다.
// layout.js / page.js
fetch('https://...', { cache: 'no-store' })
  • 다중 fetch 요청
    • 경로 세그먼트(예: 레이아웃 또는 페이지)에 여러 개의 fetch 요청이 있는 경우 세그먼트 구성 옵션을 사용하여 세그먼트에 있는 모든 데이터 요청의 캐싱 동작을 구성할 수 있습니다.
    • 예를 들어 const Dynamic = 'force-dynamic'을 사용하면 요청 시 모든 데이터를 가져오고 세그먼트가 동적으로 렌더링됩니다.
// layout.js / page.js
// Add
export const dynamic = 'force-dynamic'
  • 광범위한 세그먼트 구성 옵션 목록이 있어 경로 세그먼트의 정적 및 동적 동작을 세밀하게 제어할 수 있습니다. 자세한 내용은 API 참조를 참조하세요.

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

  • fetch를 지원하거나 노출하지 않는 타사 라이브러리(예: 데이터베이스, CMS 또는 ORM 클라이언트)를 사용하는 경우 Route Segment Config를 사용하여 해당 요청의 캐싱 및 재검증 동작을 구성할 수 있습니다. Option과 React의 캐시 기능.
  • 데이터가 캐시되는지 여부는 경로 세그먼트가 정적으로 렌더링되는지 아니면 동적으로 렌더링되는지에 따라 달라집니다. 세그먼트가 정적(기본값)인 경우 요청 출력은 경로 세그먼트의 일부로 캐시되고 재검증됩니다. 세그먼트가 동적인 경우 요청 출력은 캐시되지 않으며 세그먼트가 렌더링될 때 모든 요청에서 다시 가져옵니다.
  • 알아두면 좋은 것
    • Next.js는 개별 제3자 요청의 캐싱 및 재검증 동작을 구성하기 위한 불안정한_cache API를 개발 중입니다. (안 쓰는 게 좋겠죠?)

Example

  • 재검증 옵션은 3600으로 설정됩니다. 즉, 데이터가 최대 매시간 캐시되고 재검증된다는 의미입니다.
  • React 캐시 기능은 데이터 요청을 메모하는 데 사용됩니다.
// utils/get-items.ts
import { cache } from 'react'
 
export const revalidate = 3600 // 최대 1시간마다 데이터 재검증
 
export const getItem = cache(async (id: string) => {
  const item = await db.item.findUnique({ id })
  return item
})
  • getItem 함수가 두 번 호출되더라도 데이터베이스에 대한 쿼리는 한 번만 수행됩니다.
// app/item/layout.tsx
import { getItem } from '@/utils/get-item'
 
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 default async function Page({
  params: { id },
}: {
  params: { id: string }
}) {
  const item = await getItem(id)
  // ...
}

Route Handler를 사용하여 클라이언트에서 데이터 가져오기

  • 클라이언트 컴포넌트에서 데이터를 가져와야 하는 경우 클라이언트에서 Route Handler를 호출할 수 있습니다. Route Handler는 서버에서 실행되고 데이터를 클라이언트에 반환합니다. 이는 API 토큰과 같은 민감한 정보를 클라이언트에 노출하고 싶지 않을 때 유용합니다.
  • 예제는 Route Handler 설명서를 참조하세요.
  • 서버 컴포넌트 및 Route Handler
    • 서버 컴포넌트는 서버에서 렌더링되므로 데이터를 가져오기 위해 서버 컴포넌트에서 Route Handler를 호출할 필요가 없습니다. 대신 서버 컴포넌트 내부에서 직접 데이터를 가져올 수 있습니다.

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

  • SWR 또는 React Query와 같은 타사 라이브러리를 사용하여 클라이언트에서 데이터를 가져올 수도 있습니다. 이러한 라이브러리는 요청 메모, 캐싱, 재검증 및 데이터 변형을 위한 자체 API를 제공합니다.
  • Future APIs
    • use는 함수에서 반환된 Promise를 수락하고 처리하는 React 함수입니다. 사용 중인 fetch 래핑은 현재 클라이언트 컴포넌트에서 권장되지 않으며 여러 번 다시 렌더링될 수 있습니다. React RFC에서의 사용에 대해 자세히 알아보세요.

Reference

profile
주도적으로 문제를 정의하고 코드를 통해 해결합니다.

0개의 댓글