[Next.js] 데이터 가져오기, 캐싱, 재검증

파이리·2023년 8월 2일
0

Next.js

목록 보기
16/18
post-thumbnail

데이터 불러오기는 모든 애플리케이션의 핵심 부분입니다. 이 페이지에서는 React와 Next.js에서 데이터를 불러고오, 캐시하고, 재검증하는 방법에 대해 설명합니다.

데이터를 불러오는 방법은 크게 세 가지가 있습니다.

  • 서버에서,fetch API를 사용하는 방법
  • 서버에서, 서드파티 라이브러리를 사용하는 방법
  • 클라이언트에서 타사 서드파티 라이브러리를 사용하는 방법

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

Next.js는 네이티브 fetch 웹 API를 확장하여 서버의 각 fetch요청에 대한 캐싱 및 재검증 동작을 구성할 수 있습니다. React는 fetch를 확장하여 React 컴포넌트 트리를 랜더링하은 동안 불러오기 요청을 자동으로 메모화합니다.

서버 컴포넌트, 라우트 핸들러, 서버 액션에서 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>
}

Good to Know

  • Next.js는 cookiesheaders와 같은 서버 컴포넌트에서 데이터를 가져올 때 필요할 수 있는 유용한 함수를 제공합니다. 이러한 함수는 요청 시간 정보에 의존하기 때문에 라우트가 동적으로 랜더링됩니다.
  • 라우트 핸들러에서는 라우트 핸들러가 React 컴포넌트 트리의 일부가 아니기 때문에 가져오기 요청이 메모화되지 않습니다.
  • TypeScript가 있는 서버 컴포넌트에서 async/await을 사용하려면 TypeScript 5.1.3 이상과 @types/react 18.2.8 이상을 사용해야 합니다.

케싱 데이터

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

기본적으로 Next.js는 서버의 데이터 캐시에 가져오기 반환값을 자동으로 캐싱합니다. 즉, 빌드 시간 또는 요청 시간에 데이터를 가져와서 캐시한 후 각 데이터 요청에서 재사용할 수 있습니다.

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

POST 메서드를 사용하는 fetch 요청도 자동으로 캐시됩니다. POST 메서드를 사용하는 라우터 핸들러 내부에 있지 않는 한 캐시되지 않습니다.

데이터 캐시란 무엇인가요?

데이터 캐시는 영구 HTTP 캐시압니다. 플랫폼에 따라 캐시를 자동으로 확장하고 여러 지역에 걸쳐 공유할 수 있습니다.

데이터 재검증

재검증은 데이터 캐시를 지우고 최신 데이터를 다시 가져오는 프로세스입니다. 데이터가 변경되어 최신 정보를 표시하고 싶을 때 유용합니다.

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

  • 시간 기반 재검증 : 일정 시간이 경과한 후 자동으로 데이터의 유효성을 재검증합니다. 이 방법은 자주 변경되지 않고 최신성이 그다지 중요하지 않은 데이터에 유용합니다.

  • 온디맨드 재검증- : 이벤트를 기반으로 데이터를 수동으로 재검증합니다. 온디맨드 재검증은 태그 기반 또는 경로 기반 접근 방식을 사용하여 데이터 그룹을 한 번에 재검증 할 수 있습니다. 이 기능은 최신 데이터를 가능한 한 빨리 표시하려는 경우에 유용합니다.

시간 기반 재검증

시간 간격을 두고 데이터를 재검증하려면 fetchnext.revalidate 옵션을 사용하여 리소스의 캐시 수명을 설정할 수 있습니다.

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

또는 라우트 세그먼트의 모든 fetch 요청의 유효성을 재검증하려면 세그먼트 구성 옵션을 사용할 수 있습니다.

export const revalidate = 3600 // revalidate at most every hour

정적으로 랜더링되는 경로에 여러 개의 fetch 요청이 있고 각 요청의 재검증 빈도가 다른 경우, 모든 요청에 가장 낮은 시간이 사용됩니다. 동적으로 랜더링되는 라우트의 경우 각 fetch 요청은 독립적으로 재검증됩니다.

온디맨트 재검증

라우트 핸들러 또는 서버 액션 내부의 라우트 또는 캐시 태그로 데이터를 온디맨드 방식으로 재검증할 수 있습니다.

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

라우트 핸들러를 사용하는 경우 Next.js 앱에서만 알 수 잇는 비밀 토근을 만들어야 합니다. 이 비밀 토근은 무단 재검증 시도를 방지하는 데 사용됩니다. 예를 들어 다음 URL 구조를 사용하여 라우트에 접근할 수 있습니다.

https://<your-site.com>/api/revalidate?tag=collection&secret=<token>
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 GET(request: NextRequest) {
  // Check for secret to confirm this is a valid request
  if (
    request.nextUrl.searchParams.get('secret') !== process.env.MY_SECRET_TOKEN
  ) {
    return res.status(401).json({ message: 'Invalid token' })
  }
 
  const tag = request.nextUrl.searchParams.get('tag')
  revalidateTag(tag)
  return NextResponse.json({ revalidated: true, now: Date.now() })
}

또는 revalidatePath를 사용하여 라우트와 관련된 모든 데이터의 유효성을 재검증할 수 있습니다.

import { NextRequest, NextResponse } from 'next/server'
import { revalidatePath } from 'next/cache'
 
export async function GET(request: NextRequest) {
  const path = request.nextUrl.searchParams.get('path')
  revalidatePath(path)
  return NextResponse.json({ revalidated: true, now: Date.now() })
}

오류 처리 및 재검증

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

데이터 캐싱 옵트아웃

다음과 같은 경우 fetch 요청은 캐싱하지 않습니다.

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

개별 fetch 요청

개별 fetch 요청에 대한 캐싱을 사용하지 않으려면 fetch에서 cache 옵션을 'no-store'로 설정하면 됩니다. 이렇게 되면 모든 요청에 대해 데이터를 동적으로 가져오게 됩니다.

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

다중 fetch 요청

라우트 세그먼트에 여러 개의 fetch이 있는 경우 세그먼트 설정 옵션을 사용하여 세그먼트의 모든 데이터 요청에 대한 캐시 동작을 설정할 수 있습니다.

에를 들어 const dynamic = 'force-dynamic'을 사용하면 요청 시 모든 데이터를 가져오고 세그먼트가 동적으로 랜더링됩니다.

export const dynamic = 'auto'

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

fetch를 지원하거나 노출하지 않는 서브파티 라이브러리를 사용하는 경우, 라우트 세그먼트 옵션과 React의 cache 함수를 사용하여 해당 요청의 캐싱 및 재검증 동작을 구성할 수 있습니다.

데이터가 캐시되는지 여부는 라우트 세그먼트가 정적으로 랜더릳오디는 또는 동적으로 랜더링되는지에 따라 달라집니다. 세그먼트가 정적인 경우 요청의 결과는 캐시되고 라우트 세그먼트의 일부로 재검증됩니다. 세그먼트가 동적인 경우 요청의 결과는 캐시되지 않으며 세그먼트가 랜더링될 때마다 요청을 다시 가져옵니다.

Example

아래 예시에서는

  • revalidate 옵션이 3600으로 설정되어 있으며, 이는 최대 1시간마다 데이터를 캐시하고 재검증한다는 의미입니다.
  • React cache 함수는 데이터를 메모화하는 데 사용됩니다.
import { cache } from 'react'
 
export const revalidate = 3600 // revalidate the data at most every hour
 
export const getItem = cache(async (id: string) => {
  const item = await db.item.findUnique({ id })
  return item
})

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

import { getItem } from '@/utils/get-item'
 
export default async function Layout({
  params: { id },
}: {
  params: { id: string }
}) {
  const item = await getItem(id)
  // ...
}
import { getItem } from '@/utils/get-item'
 
export default async function Page({
  params: { id },
}: {
  params: { id: string }
}) {
  const item = await getItem(id)
  // ...
}

클라이언트에서 데이터 가져오기

클라이언트에서 데이터를 불러와야 하는 경우 SWR 또는 React Query와 같은 서드파티 라이브러리를 사용하는 것이 좋습니다. 이러한 라이브러리는 요청 메모화, 캐싱, 데이터 재검증 및 변경을 위한 자체 AP를 제공합니다.

profile
프론트엔드 개발자

0개의 댓글