데이터 가져오기, 캐싱 및 재검증
- Published on
- Next.js 애플리케이션에서 데이터를 가져오고 캐시하고 재검증하는 방법을 알아보세요.
Table of Contents
데이터 가져오기는 모든 애플리케이션의 핵심 부분입니다. 이 페이지에서는 리액트와 Next.js에서 데이터를 어떻게 가져오고 캐시하고 재검증할 수 있는지 설명합니다.
데이터를 가져올 수 있는 네 가지 방법이 있습니다.
fetch
를 사용하여 데이터 가져오기
서버에서 Next.js는 서버에서 각 fetch 요청에 대한 캐싱 및 재검증 동작을 구성할 수 있도록 기본 fetch
웹 API를 확장합니다. 리액트는 리액트 컴포넌트 트리를 렌더링하는 동안 fetch 요청을 자동으로 메모이즈합니다.
async
/await
와 함께 서버 컴포넌트에서 fetch
를 사용, 라우트 핸들러 및 서버 액션에서 fetch
를 사용할 수 있습니다.
async function getData() {
const res = await fetch('https://api.example.com/...')
// 반환 값은 *직렬화되지 않습니다*
// Date, Map, Set 등을 반환할 수 있습니다.
if (!res.ok) {
// 가장 가까운 `error.js` 오류 경계를 활성화합니다.
throw new Error('데이터를 가져오는데 실패했습니다')
}
return res.json()
}
export default async function Page() {
const data = await getData()
return <main></main>
}
JavaScript
async function getData() {
const res = await fetch('https://api.example.com/...')
// 반환 값은 *직렬화되지 않습니다*
// Date, Map, Set 등을 반환할 수 있습니다.
if (!res.ok) {
// 가장 가까운 `error.js` 오류 경계를 활성화합니다.
throw new Error('데이터를 가져오는데 실패했습니다')
}
return res.json()
}
export default async function Page() {
const data = await getData()
return <main></main>
}
데이터 캐싱
캐싱은 모든 요청에 대해 데이터 소스에서 다시 가져오지 않아도 되도록 데이터를 저장합니다. 기본적으로 Next.js는 서버의 데이터 캐시에 fetch
의 반환 값을 자동으로 캐시합니다. 이는 데이터를 빌드 시간 또는 요청 시간에 가져와 캐싱하고 각 데이터 요청에 재사용할 수 있음을 의미합니다.
// 'force-cache'는 기본값이며 생략할 수 있습니다.
fetch('https://...', { cache: 'force-cache' })
POST
방법을 사용하는 fetch
요청도 자동으로 캐시됩니다. 그러나 라우트 핸들러 내부에서 POST
방법을 사용하는 경우 캐시되지 않습니다.
데이터 캐시란?
데이터 캐시는 지속적인 HTTP 캐시입니다. 플랫폼에 따라 캐시는 자동으로 확장되며 여러 지역간에 공유될 수 있습니다. 데이터 캐시에 대한 자세한 내용을 알아보세요.
데이터 재검증
재검증은 데이터 캐시를 제거하고 최신 데이터를 다시 가져오는 과정입니다. 데이터가 변경될 때 최신 정보를 표시하려면 유용합니다.
캐시된 데이터는 두 가지 방법으로 재검증될 수 있습니다.
- 시간 기반 재검증: 일정 시간이 경과한 후 데이터를 자동으로 재검증합니다. 데이터가 자주 변경되지 않고 최신성이 그렇게 중요하지 않은 경우 유용합니다.
- 요청 기반 재검증: 이벤트(예: 폼 제출)를 기반으로 데이터를 수동으로 재검증합니다. 요청 기반 재검증은 한 번에 데이터 그룹을 재검증하기 위해 태그 기반 또는 경로 기반 접근법을 사용할 수 있습니다. Headless CMS의 내용이 업데이트될 때처럼 최신 데이터가 최대한 빨리 표시되도록 하려면 유용합니다.
시간 기반 재검증
타임 인터벌로 데이터를 재검증하려면 fetch
의 next.revalidate
옵션을 사용하여 리소스의 캐시 수명(초 단위)을 설정할 수 있습니다.
fetch('https://...', { next: { revalidate: 3600 } })
또는 라우트 세그먼트의 모든 fetch
요청을 재검증하려면 세그먼트 구성 옵션을 사용할 수 있습니다.
export const revalidate = 3600 // 최대 1시간마다 재검증
정적으로 렌더링된 라우트에 여러 개의 fetch 요청이 있고 각 요청에 다른 재검증 빈도가 있는 경우 모든 요청에 가장 낮은 시간이 사용됩니다. 동적으로 렌더링된 라우트의 경우 각 fetch
요청은 독립적으로 재검증됩니다.
시간 기반 재검증에 대한 자세한 내용을 알아보세요.
요청 기반 재검증
라우트 핸들러나 서버 액션 내에서 경로(revalidatePath
) 또는 캐시 태그(revalidateTag
)를 기반으로 데이터를 요청에 따라 재검증할 수 있습니다.
Next.js에는 라우트 간의 fetch
요청을 무효화하기 위한 캐시 태깅 시스템이 있습니다.
fetch
를 사용할 때 캐시 항목을 하나 이상의 태그로 태그 지정할 수 있는 옵션이 있습니다.- 그런 다음, 해당 태그와 연관된 모든 항목을 재검증하기 위해
revalidateTag
를 호출할 수 있습니다.
예를 들어, 아래 fetch
요청은 collection
캐시 태그를 추가합니다.
export default async function Page() {
const res = await fetch('https://...',
{ next: { tags: ['collection'] } }
)
const data = await res.json()
// ...
}
JavaScript
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'
// 예: `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
를 사용할 수 있습니다.
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
요청은 캐시되지 않습니다.
fetch
요청에cache: 'no-store'
가 추가된 경우.- 개별
fetch
요청에revalidate: 0
옵션이 추가된 경우. POST
메소드를 사용하는 라우터 핸들러 내의fetch
요청인 경우.headers
나cookies
의 사용 이후의fetch
요청인 경우.const dynamic = 'force-dynamic'
라우트 세그먼트 옵션을 사용한 경우.fetchCache
라우트 세그먼트 옵션이 기본적으로 캐시를 건너뛰도록 설정된 경우.fetch
요청이Authorization
또는Cookie
헤더를 사용하고 컴포넌트 트리에서 그 위에 캐시되지 않은 요청이 있는 경우.
fetch
요청
개별 개별 fetch
요청에 대한 캐싱을 선택 취소하려면 fetch
내의 cache
옵션을 'no-store'
로 설정할 수 있습니다. 이렇게 하면 모든 요청에서 데이터를 동적으로 가져옵니다.
fetch('https://...', { cache: 'no-store' })
fetch
API 참조에서 사용 가능한 모든cache
옵션을 확인하세요.
fetch
요청
여러 라우트 세그먼트(예: 레이아웃 또는 페이지)에 여러 fetch
요청이 있는 경우, 세그먼트 설정 옵션을 사용하여 세그먼트 내의 모든 데이터 요청의 캐싱 동작을 구성할 수 있습니다.
예를 들어, const dynamic = 'force-dynamic'
을 사용하면 모든 데이터가 요청 시간에 가져와지고 세그먼트가 동적으로 렌더링됩니다.
// 추가
export const dynamic = 'force-dynamic'
세그먼트 설정 옵션의 광범위한 목록이 있으며 라우트 세그먼트의 정적 및 동적 동작을 세밀하게 제어할 수 있습니다. 자세한 내용은 API 참조를 참조하세요.
서드 파티 라이브러리를 사용하여 서버에서 데이터 가져오기
fetch
를 지원하거나 노출하지 않는 서드 파티 라이브러리(예: 데이터베이스, CMS, ORM 클라이언트)를 사용하는 경우, 라우트 세그먼트 설정 옵션 및 리액트의 cache
함수를 사용하여 해당 요청의 캐싱 및 재검증 동작을 구성할 수 있습니다.
데이터가 캐시되는지 여부는 라우트 세그먼트가 정적 또는 동적으로 렌더링되는지에 따라 다릅니다. 세그먼트가 정적(기본값)인 경우, 요청의 출력은 라우트 세그먼트의 일부로 캐시 및 재검증됩니다. 세그먼트가 동적인 경우, 요청의 출력은 캐시되지 않고 세그먼트가 렌더링될 때마다 모든 요청에서 다시 가져옵니다.
Next.js는 개별 서드파티 요청의 캐싱 및 재검증 동작을 구성하기 위한 API,
unstable_cache
,를 개발 중입니다.
예제
import { cache } from 'react'
export const revalidate = 3600 // 데이터를 최대 매시간마다 재검증합니다.
export const getItem = cache(async (id: string) => {
const item = await db.item.findUnique({ id })
return item
})
JavaScript
import { cache } from 'react'
export const revalidate = 3600 // 데이터를 최대 매시간마다 재검증합니다.
export const getItem = cache(async (id) => {
const item = await db.item.findUnique({ id })
return item
})
revalidate
옵션은3600
으로 설정되어 있습니다. 이는 데이터가 캐시되고 최대 매시간마다 재검증됨을 의미합니다.- 리액트의
cache
함수는 데이터 요청을 메모이제이션하기 위해 사용됩니다.
아래 getItem
함수가 두 번 호출되더라도 데이터베이스에는 한 번의 쿼리만 수행됩니다.
import { getItem } from '@/utils/get-item'
export default async function Layout({
params: { id },
}: {
params: { id: string }
}) {
const item = await getItem(id)
// ...
}
JavaScript
import { getItem } from '@/utils/get-item'
export default async function Layout({ params: { id } }) {
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)
// ...
}
JavaScript
import { getItem } from '@/utils/get-item'
export default async function Page({ params: { id } }) {
const item = await getItem(id)
// ...
}
클라이언트에서 라우트 핸들러로 데이터 가져오기
클라이언트 컴포넌트에서 데이터를 가져올 필요가 있다면 클라이언트에서 라우트 핸들러를 호출할 수 있습니다. 라우트 핸들러는 서버에서 실행되어 클라이언트에 데이터를 반환합니다. 이는 API 토큰과 같은 민감한 정보를 클라이언트에 노출하지 않고자 할 때 유용합니다.
예제는 라우트 핸들러 문서에서 확인하세요.
서버 컴포넌트와 라우트 핸들러
서버 컴포넌트는 서버에서 렌더링되기 때문에 데이터를 가져오기 위해 서버 컴포넌트에서 라우트 핸들러를 호출할 필요가 없습니다. 대신 서버 컴포넌트 내부에서 데이터를 직접 가져올 수 있습니다.
서드 파티 라이브러리를 사용하여 클라이언트에서 데이터 가져오기
SWR 또는 리액트 Query와 같은 서드 파티 라이브러리를 사용하여 클라이언트에서 데이터를 가져올 수도 있습니다. 이 라이브러리들은 요청 메모이제이션, 캐싱, 재검증 및 데이터 변경에 대한 자체 API를 제공합니다.
미래의 API:
use
는 함수가 반환하는 promise를 수락하고 처리하는 리액트 함수입니다. 클라이언트 컴포넌트에서fetch
를use
로 감싸는 것은 현재 권장되지 않으며 여러 번의 리렌더링을 유발할 수 있습니다. 리액트 RFC에서use
에 대해 자세히 알아보세요.