Next.js Caching
해당 글은 Next.js 공식문서를 번역한 글입니다.
참고) unstable_cache는 archive 되었고 이를 대신하는 실험중인 기능입니다.
This API is currently experimental and subject to change.
use cache는 캐시할 컴포넌트, 함수 또는 파일을 지정합니다.
파일 상단에 사용하여 파일의 모든 함수를 캐시할 수 있음을 나타내거나 함수 상단에 인라인으로 사용하여 함수를 캐시할 수 있는 것으로 표시할 수 있습니다.
이것은 실험적인 Next.js 기능이며, use client나 use server와 같은 native React 기능이 아닙니다.
Next.config.ts 파일에서 dynamicIO 플래그를 사용하여 use cache 지시어에 대한 지원을 활성화할 수 있습니다.
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
experimental: {
dynamicIO: true,
},
}
export default nextConfig
use cache
캐싱은 렌더링 또는 데이터 요청의 결과를 저장하여 웹 애플리케이션의 성능을 향상시키는 데 사용되는 기술입니다.
request-time data에 의존하는 APIs나 비동기 함수를 사용할 때마다 Next.js는 자동으로 동적 렌더링을 선택합니다.
use cache를 사용하여 이러한 작업의 결과를 명시적으로 caching하고 애플리케이션의 렌더링 성능을 최적화할 수 있습니다.
use cache는 unstable_cache 함수를 대체하는 것을 목표로 하는 실험적인 기능입니다.
JSON 데이터 캐싱으로 제한되고 재검증 기간과 태그를 수동으로 정의해야 하는 unstable_cache와 달리 use cache는 더 많은 유연성을 제공합니다. 이 함수를 사용하면 데이터 불러오기 출력과 컴포넌트 출력뿐만 아니라 React 서버 컴포넌트(RSC)가 직렬화할 수 있는 모든 데이터를 포함하여 더 넓은 범위의 데이터를 캐싱할 수 있습니다.
또한 use cache를 사용하면 입력과 출력을 모두 추적하여 복잡성을 자동으로 관리하므로, 실수로 cache를 poison 할 가능성이 줄어듭니다. 입력과 출력을 모두 직렬화하므로 잘못된 캐시 검색으로 인한 문제를 방지할 수 있습니다.
Next.js에서 'use cache' 지시문을 사용하면 전체 경로, 컴포넌트 및 함수의 반환값을 캐시할 수 있습니다.
비동기 함수가 있는 경우 파일 상단이나 함수 범위 내에 'use cache'를 추가하여 캐시 가능으로 표시할 수 있습니다.
이렇게 하면 반환값을 캐시하여 후속 렌더링에 재사용할 수 있음을 Next.js에 알립니다.
// File level
'use cache'
export default async function Page() {
// ...
}
// Component level
export async function MyComponent() {
'use cache'
return <></>
}
// Function level
export async function getData() {
'use cache'
const data = await fetch('/api/data')
return data
}
Revalidating
Next.js는 'use cache' 지시문을 사용할 때 재검증 기간을 기본 15분으로 설정합니다.
Next.js는 거의 무한대에 가까운 만료 기간을 설정하므로 자주 업데이트할 필요가 없는 콘텐츠에 적합합니다.
이 재검증 기간은 자주 변경되지 않을 것으로 예상되는 콘텐츠에 유용할 수 있지만,
cacheLife 및 cacheTag API를 사용하여 캐시 동작을 구성할 수 있습니다:
- cacheLife: time-based 재검증 기간의 경우
- cacheTag: on-demand 재검증용
이 두 API는 모두 클라이언트 및 서버 캐싱 계층에 통합되므로 한 곳에서 캐싱 시맨틱을 구성하고 모든 곳에 적용할 수 있습니다.
아래 예는 function level에서 cacheLife 함수를 사용하여 함수 출력에 1일의 재검증 기간을 설정하는 방법을 보여줍니다:
import { unstable_cacheLife as cacheLife } from 'next/cache'
export async function MyComponent() {
async function getData() {
'use cache'
cacheLife('days')
const data = await fetch('/api/data')
return data
}
return // Use the data here
}
cache revalidation 작동 방식
재검증 기간을 15분으로 설정하면 아래와 같이 작동합니다.
- cache HIT : 15분 내에 요청이 이루어지면 캐시된 데이터가 제공되며 cache HIT입니다.
- Stale data : 15분 이후에 요청이 발생하면 캐시된 값은 여전히 제공되지만 오래된 데이터로 간주됩니다.
Next.js는 백그라운드에서 새 캐시 항목을 다시 계산합니다. - Cache MISS : 캐시 항목이 만료된 후 후속 요청이 발생하면 Next.js는 이를 cache MISS로 처리하고 데이터를 다시 계산하여 소스에서 다시 가져옵니다.
Time-based revalidation with cacheLife
cacheLife 함수는 'use cache' 지시어가 있는 경우에만 사용할 수 있으며,
cache profiles를 기반으로 time-based revalidation 기간을 정의할 수 있습니다.
캐싱 동작을 명시적으로 정의하기 위해 'use cache' 지시문을 사용할 때는 항상 cache profiles을 추가하는 것이 좋습니다.
cache profiles는 다음과 같은 속성을 포함하는 객체입니다
“stale” 속성은 client-side router caching을 구체적으로 제어한다는 점에서 staleTimes 설정과 다릅니다.
staleTimes는 동적 및 정적 데이터의 모든 인스턴스에 영향을 미치는 전역 설정인 반면,
cacheLife 구성을 사용하면 기능별 또는 라우트별로 “stale(오래된)” 시간을 정의할 수 있습니다.
“stale” 속성은 Cache-control: max-age 헤더를 설정하지 않습니다. 대신 client-side router cache를 제어합니다.
Default cache profiles
Next.js는 다양한 timescales로 모델링된 명명된 cache profiles 집합을 제공합니다.
'use cache' 지시어와 함께 cacheLife 함수에서 캐시 프로파일을 지정하지 않으면 Next.js는 자동으로 “default” cache profile을 적용합니다.
'use cache'
import { unstable_cacheLife as cacheLife } from 'next/cache'
cacheLife('minutes')
캐시 프로필을 참조하는 데 사용되는 문자열 값은 특별한 의미를 갖지 않고 의미론적 labels 역할을 합니다.
이를 통해 코드베이스 내에서 캐시된 콘텐츠를 더 잘 이해하고 관리할 수 있습니다.
Defining reusable cache profiles
재사용 가능한 캐시 프로필은 next.config.ts 파일에 정의하여 만들 수 있습니다.
사용 사례에 맞는 이름을 선택하고 오래된, 재검증 및 만료 속성에 대한 값을 설정하세요.
필요한 만큼 사용자 정의 캐시 프로필을 만들 수 있습니다.
각 프로필은 cacheLife 함수에 전달된 문자열 값으로 이름을 참조할 수 있습니다.
아래의 예는 14일 동안 캐시를 저장하고, 매일 업데이트를 확인하며, 14일 후에 캐시를 만료합니다.
그런 다음 애플리케이션 전체에서 이 프로필을 이름(ex. biweekly)으로 참조할 수 있습니다
const nextConfig = {
experimental: {
dynamicIO: true,
cacheLife: {
biweekly: {
stale: 60 * 60 * 24 * 14, // 14 days
revalidate: 60 * 60 * 24, // 1 day
expire: 60 * 60 * 24 * 14, // 14 days
},
},
},
}
module.exports = nextConfig
Overriding the default cache profiles
기본 캐시 프로필은 캐시 가능한 출력의 특정 부분이 얼마나 fresh하거나 stale한지를를 고려하는 데 유용한 방법을 제공하지만,
애플리케이션 캐싱 전략에 더 잘 맞추기 위해 다른 이름의 프로필을 선호할 수도 있습니다.
기본값과 같은 이름의 새 구성을 만들어 기본 명명된 캐시 프로필을 재정의할 수 있습니다.
아래 예는 기본 'days' 캐시 프로필을 재정의하는 방법을 보여줍니다.
const nextConfig = {
experimental: {
dynamicIO: true,
cacheLife: {
days: {
stale: 3600, // 1 hour
revalidate: 900, // 15 minutes
expire: 86400, // 1 day
},
},
},
}
module.exports = nextConfig
Defining cache profiles inline
특정 사용 사례의 경우 cacheLife 함수에 객체를 전달하여 사용자 정의 캐시 프로필을 설정할 수 있습니다.
'use cache'
import { unstable_cacheLife as cacheLife } from 'next/cache'
cacheLife({
stale: 3600, // 1 hour
revalidate: 900, // 15 minutes
expire: 86400, // 1 day
})
// rest of code
이 인라인 캐시 프로필은 생성된 함수 또는 파일에만 적용됩니다.
애플리케이션 전체에서 동일한 프로필을 재사용하려면 다음.config.ts 파일의 cacheLife 속성에 구성을 추가하면 됩니다.
Nested usage of use cache and cacheLife
동일한 경로 또는 컴포넌트 트리에서 여러 캐싱 동작을 정의할 때 내부 캐시가 자체 cacheLife 프로파일을 지정하면 외부 캐시는 그 중 가장 짧은 캐시 기간을 준수합니다. 이는 외부 캐시에 자체적인 명시적 cacheLife 프로파일이 정의되어 있지 않은 경우에만 적용됩니다.
캐시 경계에 대한 결정 계층 구조
- Next.js는 내부 사용 캐시 지시문을 제외한 전체 'use cache' 경계 내에서 가장 짧은 캐시 프로필을 사용합니다.
- 캐시 프로파일이 존재하지 않으면 모든 내부 사용 캐시 호출에서 가장 짧은 프로파일 시간이 이 사용 캐시에 적용됩니다.
내부 사용 캐시가 없는 경우 기본값이 사용됩니다. - two levels 깊이의 내부 캐시는 이미 부모 캐시에 기간을 제공했기 때문에 외부 캐시에 영향을 미치지 않습니다.
예를 들어 캐시 프로파일을 지정하지 않고 페이지에 사용 캐시 지시문을 추가하면 기본 캐시 프로파일이 암시적으로 적용됩니다(cacheLife(“default”)). 페이지로 가져온 컴포넌트가 자체 캐시 프로파일과 함께 use cache 지시문을 사용하는 경우 외부 및 내부 캐시 프로파일을 비교하여 프로파일에 설정된 최단 기간을 적용합니다.
// Parent component
import { unstable_cacheLife as cacheLife } from 'next/cache'
import { ChildComponent } from './child'
export async function ParentComponent() {
'use cache'
cacheLife('days')
return (
<div>
<ChildComponent />
</div>
)
}
// Child component
import { unstable_cacheLife as cacheLife } from 'next/cache'
export async function ChildComponent() {
'use cache'
cacheLife('hours')
return <div>Child Content</div>
// This component's cache will respect the shorter 'hours' profile
}
Revalidate on-demand with cacheTag
캐시 데이터를 on-demand 방식으로 제거하기 위해 cacheTag를 revalidateTag와 함께 사용합니다.
cacheTag 함수는 단일 문자열 값 또는 문자열 배열을 받습니다.
revalidateTag(tag: string): void;
Tag : 재검증하려는 데이터와 연결된 캐시 태그를 나타내는 문자열입니다. 256자 이하여야 하며 대소문자를 구분합니다.
fetch에 tag를 아래와같이 추가할 수 있습니다.
fetch(url, { next: { tags: [...] } });
revalidateTag는 값을 return 하지 않습니다.
예제) Server action
// app/actions.ts
'use server'
import { revalidateTag } from 'next/cache'
export default async function submit() {
await addPost()
revalidateTag('posts')
}
ex) Route Handler
// app/api/revalidate/route.ts
import type { NextRequest } from 'next/server'
import { revalidateTag } from 'next/cache'
export async function GET(request: NextRequest) {
const tag = request.nextUrl.searchParams.get('tag')
revalidateTag(tag)
return Response.json({ revalidated: true, now: Date.now() })
}
다음 예제에서 getData 함수는 “weeks” 캐시 프로필을 사용하고, 함수 캐시 출력에 cacheTag를 정의합니다:
// app/actions.ts
import {
unstable_cacheTag as cacheTag,
unstable_cacheLife as cacheLife,
} from 'next/cache'
export async function getData() {
'use cache'
cacheLife('weeks')
cacheTag('my-data')
const data = await fetch('/api/data')
return data
}
그런 다음 다른 함수(ex: route handler 또는 Server Action)에서 revalidateTag API를 사용하여 on-demand 방식으로 cache를 제거할 수 있습니다.
'use server'
import { revalidateTag } from 'next/cache'
export default async function submit() {
await addPost()
revalidateTag('my-data')
}
- revalidateTag는 Node.js와 Edge 런타임 모두에서 사용할 수 있습니다.
- revalidateTag는 경로가 다음에 방문될 때만 캐시를 무효화합니다.
즉, 동적 경로 세그먼트를 사용하여 재검증태그를 호출해도 한 번에 많은 재검증이 즉시 트리거되지 않습니다.
무효화는 경로가 다음에 방문될 때만 발생합니다.
사용 예시
Caching component output with use cache
component level에서 'use cache'를 사용하여 해당 컴포넌트 내에서 수행되는 모든 가져오기 또는 계산을 캐시할 수 있습니다.
애플리케이션 전체에서 컴포넌트를 재사용할 때 프로퍼티가 동일한 구조를 유지하는 한 동일한 캐시 항목을 공유할 수 있습니다.
프로퍼티는 직렬화되어 캐시 키의 일부를 형성합니다. 애플리케이션의 여러 위치에서 동일한 컴포넌트를 사용하는 경우 직렬화된 프로퍼티가 각 인스턴스에서 동일한 값을 생성하는 한 캐시 항목이 재사용됩니다.
import { unstable_cacheLife as cacheLife } from 'next/cache'
interface BookingsProps {
type: string
}
export async function Bookings({ type = 'massage' }: BookingsProps) {
'use cache'
cacheLife('minutes')
async function getBookingsData() {
const data = await fetch(`/api/bookings?type=${encodeURIComponent(type)}`)
return data
}
return //...
}
Caching function output with use cache
모든 비동기 함수에 'use cache'를 추가할 수 있으므로 컴포넌트나 경로만 캐싱하는 데 제한되지 않습니다.
네트워크 요청이나 데이터베이스 쿼리를 캐시하거나 매우 느린 작업을 계산하고 싶을 수도 있습니다.
이러한 유형의 작업이 포함된 함수에 'use cache'를 추가하면 캐시가 가능해지며 재사용 시 동일한 캐시 항목을 공유하게 됩니다.
// app/actions.ts
import { unstable_cacheLife as cacheLife } from 'next/cache'
export async function getData() {
'use cache'
cacheLife('minutes')
const data = await fetch('/api/data')
return data
}
https://nextjs.org/docs/app/api-reference/directives/use-cache