이 글은 Next.js 문서 중 캐싱(Caching)에 관한 내용을 번역한 것으로, 참고용으로만 사용하시길 바랍니다. 실제 문서를 꼼꼼히 읽어보는 것을 권장드립니다.
Next.js는 렌더링 작업과 데이터 요청을 캐싱하여 애플리케이션의 성능을 향상시키고 비용을 절감합니다. 이 페이지에서는 Next.js 캐싱 메커니즘, 이를 구성할 수 있는 API 및 상호 작용 방식을 자세히 설명합니다.
참고: 이 페이지는 Next.js의 내부 동작 방식을 이해하는 데 도움이 되지만, Next.js를 생산적으로 사용하는 데 필수적인 지식은 아닙니다. 대부분의 Next.js 캐싱 휴리스틱은 API 사용에 따라 결정되며, 최소한의 구성으로 최고의 성능을 제공합니다.
다음은 다양한 캐싱 메커니즘과 그 목적에 대한 개요입니다:
기본적으로 Next.js는 성능을 향상시키고 비용을 절감하기 위해 가능한 한 많은 것을 캐시합니다. 이는 경로가 정적으로 렌더링되고 데이터 요청이 캐시된다는 것을 의미하며, 사용자가 이를 옵트아웃하지 않는 한 그렇습니다. 아래 다이어그램은 빌드 시점과 정적 경로가 처음 방문되었을 때의 기본 캐싱 동작을 보여줍니다.
캐싱 동작은 경로가 정적으로 렌더링되는지 동적으로 렌더링되는지, 데이터가 캐시되는지 캐시되지 않는지, 요청이 초기 방문의 일부인지 후속 탐색의 일부인지에 따라 달라집니다. 사용 사례에 따라 개별 경로 및 데이터 요청에 대한 캐싱 동작을 구성할 수 있습니다.
React는 동일한 URL과 옵션을 가진 요청을 자동으로 메모화하도록 fetch API를 확장합니다. 이는 React 컴포넌트 트리의 여러 위치에서 동일한 데이터를 요청해도 한 번만 실행됨을 의미합니다.
예를 들어, 경로 전체에서 동일한 데이터를 사용해야 하는 경우(예: 레이아웃, 페이지 및 여러 구성 요소에서) 트리의 최상위에서 데이터를 가져오고 구성 요소 간에 props를 전달할 필요가 없습니다. 대신, 동일한 데이터를 네트워크를 통해 여러 번 요청하는 것에 대한 성능 영향을 걱정하지 않고 데이터를 필요로 하는 구성 요소에서 데이터를 가져올 수 있습니다.
async function getItem() {
// The `fetch` function is automatically memoized and the result
// is cached
const res = await fetch('https://.../item/1')
return res.json()
}
// This function is called twice, but only executed the first time
const item = await getItem() // cache MISS
// The second call could be anywhere in your route
const item = await getItem() // cache HIT
1. 경로를 렌더링할 때, 특정 요청이 처음 호출될 때 그 결과는 메모리에 없으며 캐시 미스가 발생합니다.
2. 따라서 함수가 실행되고, 외부 소스에서 데이터를 가져와서 결과를 메모리에 저장합니다.
3. 같은 렌더링 패스에서 동일한 요청의 후속 함수 호출은 캐시 히트를 발생시키며, 함수가 실행되지 않고 메모리에서 데이터를 반환합니다.
4.경로가 렌더링되고 렌더링 패스가 완료되면 메모리가 "재설정"되고 모든 요청 메모화 항목이 지워집니다.
참고:
- 요청 메모화는 React 기능이지 Next.js 기능이 아닙니다. 다른 캐싱 메커니즘과 상호 작용하는 방식을 보여주기 위해 여기에 포함되었습니다.
- 메모화는 fetch 요청의 GET 메서드에만 적용됩니다.
- 메모화는 React 구성 요소 트리에만 적용됩니다. 이는 다음을 의미합니다:
- generateMetadata, generateStaticParams, 레이아웃, 페이지 및 기타 서버 구성 요소의 fetch 요청에 적용됩니다.
- Route Handlers의 fetch 요청에는 적용되지 않습니다. 이는 React 구성 요소 트리의 일부가 아니기 때문입니다.
- fetch가 적합하지 않은 경우(예: 일부 데이터베이스 클라이언트, CMS 클라이언트 또는 GraphQL 클라이언트)에는 React cache 함수를 사용하여 함수를 메모화할 수 있습니다.
캐시는 서버 요청의 수명 동안 유지되며, React 컴포넌트 트리가 렌더링될 때까지 지속됩니다.
메모화는 서버 요청 간에 공유되지 않으며 렌더링 중에만 적용되기 때문에 재검증이 필요하지 않습니다.
메모화는 fetch 요청의 GET 메소드에만 적용되며, POST 및 DELETE와 같은 다른 메소드에는 적용되지 않습니다. 이 기본 동작은 React 최적화이며, 옵트 아웃을 권장하지 않습니다.
Next.js에는 데이터 캐시가 내장되어 있어 서버 요청 및 배포 간에 데이터 가져오기 결과를 유지합니다. 이는 Next.js가 기본 fetch API를 확장하여 서버에서 각 요청이 고유한 지속 캐싱 의미론을 설정할 수 있게 합니다.
참고:
브라우저에서는 fetch의 캐시 옵션이 브라우저의 HTTP 캐시와 상호 작용하는 방식을 나타내는 반면, Next.js에서는 서버 측 요청이 서버의 데이터 캐시와 상호 작용하는 방식을 나타냅니다.
기본적으로 fetch를 사용하는 데이터 요청은 캐시됩니다. fetch의 캐시 및 next.revalidate 옵션을 사용하여 캐싱 동작을 구성할 수 있습니다.
두 가지 캐싱 메커니즘 모두 캐시된 데이터를 재사용하여 성능을 향상시키지만, 데이터 캐시는 들어오는 요청과 배포 간에 지속되며, 메모화는 요청의 수명 동안만 지속됩니다.
메모화를 통해 렌더링 서버에서 데이터 캐시 서버(예: CDN 또는 Edge 네트워크) 또는 데이터 소스(예: 데이터베이스 또는 CMS)로의 네트워크 경계를 넘어야 하는 중복 요청 수를 줄일 수 있습니다. 데이터 캐시를 통해 원본 데이터 소스로의 요청 수를 줄일 수 있습니다.
데이터 캐시는 들어오는 요청과 배포 간에 지속되며, 재검증하거나 옵트 아웃하지 않는 한 유지됩니다.
캐시된 데이터는 두 가지 방법으로 재검증할 수 있습니다:
타임 인터벌에서 데이터를 재검증하려면 fetch의 next.revalidate 옵션을 사용하여 리소스의 캐시 수명을 설정할 수 있습니다(초 단위).
// 최대 1시간마다 재검증
fetch('https://...', { next: { revalidate: 3600 } });
또한 Route Segment Config 옵션을 사용하여 세그먼트 내의 모든 fetch 요청을 구성하거나 fetch를 사용할 수 없는 경우를 처리할 수 있습니다.
데이터는 경로(revalidatePath) 또는 캐시 태그(revalidateTag)로 온디맨드 재검증할 수 있습니다.
개별 데이터 요청에 대해 캐싱을 옵트 아웃하려면 cache 옵션을 no-store로 설정합니다. 이렇게 하면 fetch가 호출될 때마다 데이터를 가져옵니다.
// 개별 fetch 요청의 캐시 옵트 아웃
fetch(`https://...`, { cache: 'no-store' });
또는 특정 경로 세그먼트의 캐싱을 옵트 아웃하려면 Route Segment Config 옵션을 사용할 수 있습니다. 이는 경로 세그먼트의 모든 데이터 요청에 영향을 미칩니다.
// 경로 세그먼트의 모든 데이터 요청에 대해 캐시 옵트 아웃
export const dynamic = 'force-dynamic';
참고: 데이터 캐시는 현재 페이지/경로에서만 사용할 수 있습니다. 미들웨어 내부에서 수행된 fetch는 기본적으로 캐시되지 않습니다.
관련 용어:
자동 정적 최적화(Automatic Static Optimization), 정적 사이트 생성(Static Site Generation), 또는 정적 렌더링(Static Rendering)이라는 용어가 애플리케이션의 경로를 빌드 시 렌더링하고 캐싱하는 과정을 가리키기 위해 상호 교환적으로 사용되는 것을 볼 수 있습니다.
Next.js는 빌드 시 경로를 자동으로 렌더링하고 캐싱합니다. 이는 모든 요청마다 서버에서 렌더링하는 대신 캐시된 경로를 제공할 수 있게 해주는 최적화로, 더 빠른 페이지 로드를 가능하게 합니다.
전체 경로 캐시가 어떻게 작동하는지 이해하려면 React가 렌더링을 처리하는 방식과 Next.js가 결과를 캐싱하는 방식을 살펴보는 것이 유용합니다:
서버에서 Next.js는 React의 API를 사용하여 렌더링을 조정합니다. 렌더링 작업은 개별 경로 세그먼트와 Suspense 경계로 분할됩니다.
각 청크는 두 단계로 렌더링됩니다:
Next.js의 기본 동작은 서버에서 경로의 렌더링 결과(React 서버 컴포넌트 페이로드와 HTML)를 캐싱하는 것입니다. 이는 빌드 시점에 정적으로 렌더링된 경로 또는 재검증 중에 적용됩니다.
요청 시 클라이언트에서:
React 서버 컴포넌트 페이로드는 클라이언트 측 라우터 캐시에 저장됩니다. 이 캐시는 개별 경로 세그먼트로 분할되어 사용자 세션 동안 유지됩니다. 이는 이전에 방문한 경로를 저장하고 미래 경로를 프리페치하여 내비게이션 경험을 개선합니다.
후속 내비게이션이나 프리페치 중 Next.js는 React 서버 컴포넌트 페이로드가 라우터 캐시에 저장되어 있는지 확인합니다. 만약 그렇다면, 서버에 새로운 요청을 보내지 않습니다.
경로 세그먼트가 캐시에 없으면, Next.js는 서버에서 React 서버 컴포넌트 페이로드를 가져와 클라이언트의 라우터 캐시에 저장합니다.
경로가 빌드 시점에 캐시되는지 여부는 정적 렌더링 또는 동적 렌더링 여부에 따라 달라집니다. 정적 경로는 기본적으로 캐시되며, 동적 경로는 요청 시점에 렌더링되어 캐시되지 않습니다.
정적 경로는 빌드 시점 또는 데이터 재검증 후에 캐시되며, 동적 경로는 요청 시점에 캐시되지 않습니다.
기본적으로 전체 경로 캐시는 지속적입니다. 즉, 렌더링 출력은 사용자 요청 간에 캐시됩니다.
전체 경로 캐시를 무효화하는 방법은 두 가지가 있습니다:
전체 경로 캐시를 옵트 아웃하거나, 즉, 들어오는 모든 요청에 대해 동적으로 컴포넌트를 렌더링하려면:
Next.js는 사용자 세션 동안 React 서버 컴포넌트 페이로드를 개별 경로 세그먼트로 분할하여 저장하는 클라이언트 측 인메모리 캐시를 가지고 있습니다. 이를 라우터 캐시라고 합니다.
사용자가 경로 간을 탐색할 때, Next.js는 방문한 경로 세그먼트를 캐시하고 사용자가 탐색할 가능성이 있는 경로를 프리페치합니다( <Link>
컴포넌트의 뷰포트 내에 있는 경로).
이로 인해 사용자는 다음과 같은 개선된 내비게이션 경험을 할 수 있습니다:
라우터 캐시는 사용자의 세션 동안 브라우저에서 React 서버 컴포넌트 페이로드를 일시적으로 저장하며, 전체 경로 캐시는 서버에서 여러 사용자 요청 간에 React 서버 컴포넌트 페이로드와 HTML을 지속적으로 저장합니다.
전체 경로 캐시는 정적으로 렌더링된 경로만 캐시하는 반면, 라우터 캐시는 정적 및 동적으로 렌더링된 경로 모두에 적용됩니다.
라우터 캐시는 브라우저의 임시 메모리에 저장됩니다. 두 가지 요소가 라우터 캐시의 지속 시간을 결정합니다:
페이지 새로고침은 모든 캐시된 세그먼트를 지우지만, 자동 무효화 기간은 프리페칭된 개별 세그먼트에만 영향을 미칩니다.
라우터 캐시에서 옵트 아웃하는 것은 불가능하지만, router.refresh, revalidatePath, 또는 revalidateTag를 호출하여 무효화할 수 있습니다. 이를 통해 캐시가 지워지고 서버에 새로운 요청이 전송되어 최신 데이터를 표시합니다.
또한 <Link>
컴포넌트의 prefetch 속성을 false로 설정하여 프리페칭을 옵트 아웃할 수 있습니다. 그러나 이는 임시로 30초 동안 경로 세그먼트를 저장하여 중첩된 세그먼트 간 빠른 내비게이션을 가능하게 합니다. 방문한 경로는 여전히 캐시됩니다.
라우터 캐시를 무효화하는 두 가지 방법이 있습니다:
서버 액션에서:
router.refresh를 호출하여 현재 경로에 대한 서버 요청을 새로 고칩니다.
다양한 캐싱 메커니즘을 구성할 때 이들이 서로 어떻게 상호작용하는지 이해하는 것이 중요합니다:
데이터 캐시와 전체 경로 캐시
다음은 다양한 Next.js API가 캐싱에 어떻게 영향을 미치는지에 대한 개요입니다:
<Link>
기본적으로 <Link>
컴포넌트는 전체 경로 캐시에서 경로를 자동으로 프리페치하고 React 서버 컴포넌트 페이로드를 라우터 캐시에 추가합니다.
프리페칭을 비활성화하려면 prefetch 속성을 false로 설정할 수 있습니다. 그러나 이는 임시로 30초 동안 경로 세그먼트를 저장하여 중첩된 세그먼트 간 빠른 내비게이션을 가능하게 합니다. 방문한 경로는 여전히 캐시됩니다.
useRouter 훅의 prefetch 옵션을 사용하여 경로를 수동으로 프리페치할 수 있습니다. 이는 React 서버 컴포넌트 페이로드를 라우터 캐시에 추가합니다.
useRouter 훅의 refresh 옵션을 사용하여 경로를 수동으로 새로 고칠 수 있습니다. 이는 라우터 캐시를 완전히 지우고 현재 경로에 대해 서버에 새로운 요청을 보냅니다. refresh는 데이터 캐시나 전체 경로 캐시에 영향을 미치지 않습니다.
fetch로 반환된 데이터는 자동으로 데이터 캐시에 캐시됩니다.
// 기본적으로 캐시됨. `force-cache`는 기본 옵션이므로 생략 가능합니다.
fetch(`https://...`, { cache: 'force-cache' });
추가 옵션은 fetch API 레퍼런스를 참조하세요.
개별 fetch 요청의 데이터를 캐시하지 않으려면 cache 옵션을 no-store로 설정할 수 있습니다:
// 캐시 옵트 아웃
fetch(`https://...`, { cache: 'no-store' });
렌더링 출력이 데이터에 의존하기 때문에 cache: 'no-store'를 사용하면 해당 경로의 전체 경로 캐시도 건너뛰게 됩니다. 즉, 해당 경로는 모든 요청 시점에 동적으로 렌더링됩니다.
fetch의 next.revalidate 옵션을 사용하여 개별 fetch 요청의 재검증 기간(초 단위)을 설정할 수 있습니다. 이는 데이터 캐시를 재검증하며, 이는 전체 경로 캐시를 재검증합니다. 새로운 데이터가 가져오고, 서버에서 컴포넌트를 다시 렌더링하여 새로운 렌더링 출력을 캐시합니다.
// 최대 1시간마다 재검증
fetch(`https://...`, { next: { revalidate: 3600 } });
Next.js에는 세분화된 데이터 캐싱과 재검증을 위한 캐시 태그 시스템이 있습니다.
fetch 또는 unstable_cache를 사용할 때 하나 이상의 태그로 캐시 항목을 태그할 수 있습니다. 그런 다음, revalidateTag를 호출하여 해당 태그와 연결된 캐시 항목을 제거할 수 있습니다.
예를 들어, 데이터를 가져올 때 태그를 설정할 수 있습니다:
// 태그로 데이터 캐시
fetch(`https://...`, { next: { tags: ['a', 'b', 'c'] } });
그런 다음, revalidateTag를 호출하여 태그와 연결된 캐시 항목을 제거할 수 있습니다:
// 특정 태그로 항목 재검증
revalidateTag('a');
revalidateTag를 사용할 수 있는 두 가지 장소가 있으며, 이를 통해 달성하려는 목표에 따라 다릅니다:
revalidatePath를 사용하면 단일 작업에서 특정 경로 아래의 데이터와 경로 세그먼트를 수동으로 재검증하고 다시 렌더링할 수 있습니다. revalidatePath 메서드를 호출하면 데이터 캐시를 재검증하여 전체 경로 캐시를 무효화합니다.
revalidatePath('/');
revalidatePath를 사용할 수 있는 두 가지 장소가 있으며, 이를 통해 달성하려는 목표에 따라 다릅니다:
router.refresh를 호출하면 라우터 캐시가 지워지고 서버에서 현재 경로에 대한 새로운 요청이 이루어지지만, 데이터 캐시나 전체 경로 캐시에는 영향을 미치지 않습니다.
차이점은 revalidatePath가 데이터 캐시와 전체 경로 캐시를 무효화하는 반면, router.refresh는 클라이언트 측 API로 데이터 캐시와 전체 경로 캐시를 변경하지 않습니다.
cookies, headers, searchParams와 같은 동적 함수 및 Pages의 searchParams prop은 런타임의 요청 정보를 기반으로 합니다. 이를 사용하면 전체 경로 캐시를 옵트 아웃합니다. 즉, 경로는 동적으로 렌더링됩니다.
서버 액션에서 cookies.set 또는 cookies.delete를 사용하면 쿠키를 사용하는 경로가 오래되지 않도록 라우터 캐시를 무효화합니다 (예: 인증 상태를 반영).
Route Segment Config 옵션을 사용하면 경로 세그먼트 기본값을 재정의하거나 fetch API를 사용할 수 없는 경우를 처리할 수 있습니다(예: 데이터베이스 클라이언트 또는 타사 라이브러리).
다음 Route Segment Config 옵션은 데이터 캐시와 전체 경로 캐시를 옵트 아웃합니다:
export const dynamic = 'force-dynamic';
export const revalidate = 0;
동적 세그먼트(예: app/blog/[slug]/page.js)의 경우 generateStaticParams에 의해 제공된 경로는 빌드 시 전체 경로 캐시에 캐시됩니다. 요청 시점에는 빌드 시점에 알려지지 않은 경로도 첫 번째 방문 시 캐시됩니다.
요청 시점의 캐싱을 비활성화하려면 경로 세그먼트에서 dynamicParams = false 옵션을 사용할 수 있습니다. 이 구성 옵션이 사용되면 generateStaticParams에 의해 제공된 경로만 제공되며, 다른 경로는 404로 처리되거나 일치(포괄적 경로의 경우)됩니다.
추가 정보는 generateStaticParams API 레퍼런스를 참조하세요.
React cache 함수는 함수의 반환 값을 메모화하여 동일한 함수를 여러 번 호출할 때 한 번만 실행되도록 합니다.
fetch 요청은 자동으로 메모화되기 때문에 이를 React cache로 감쌀 필요가 없습니다. 그러나 fetch API가 적합하지 않은 경우 수동으로 데이터 요청을 메모화할 수 있습니다. 예를 들어, 일부 데이터베이스 클라이언트, CMS 클라이언트 또는 GraphQL 클라이언트의 경우입니다.
// utils/get-item.ts
import { cache } from 'react';
import db from '@/lib/db';
export const getItem = cache(async (id: string) => {
const item = await db.item.findUnique({ id });
return item;
});