문서
App Router 사용하기
캐싱

Next.js의 캐싱

Next.js는 렌더링 작업과 데이터 요청을 캐싱하여 애플리케이션의 성능을 향상시키고 비용을 절감합니다. 이 페이지에서는 Next.js의 캐싱 메커니즘, 이를 구성하는 데 사용할 수 있는 API, 그리고 이들이 서로 어떻게 상호작용하는지에 대해 자세히 살펴봅니다.

알아두면 좋은 점: 이 페이지는 Next.js가 내부적으로 어떻게 작동하는지 이해하는 데 도움이 되지만, Next.js를 생산적으로 사용하는 데 필수적인 지식은 아닙니다. Next.js의 대부분의 캐싱 방식은 API 사용에 따라 결정되며, 최상의 성능을 위해 제로 또는 최소한의 구성으로 기본값이 설정되어 있습니다. 대신 예제로 바로 넘어가고 싶다면, 여기서 시작하세요.

개요

다음은 다양한 캐싱 메커니즘과 그 목적에 대한 개요입니다:

메커니즘무엇을어디서목적지속 기간
요청 메모이제이션함수의 반환 값서버React 컴포넌트 트리에서 데이터 재사용요청 수명 주기당
데이터 캐시데이터서버사용자 요청과 배포 간 데이터 저장지속적 (재검증 가능)
전체 경로 캐시HTML 및 RSC 페이로드서버렌더링 비용 감소 및 성능 향상지속적 (재검증 가능)
라우터 캐시RSC 페이로드클라이언트탐색 시 서버 요청 감소사용자 세션 또는 시간 기반

기본적으로 Next.js는 성능을 향상시키고 비용을 줄이기 위해 가능한 한 많이 캐시합니다. 이는 기본 설정에서 제외하지 않는 한 경로가 정적으로 렌더링되고 데이터 요청이 캐시된다는 것을 의미합니다. 아래 다이어그램은 기본 캐싱 동작을 보여줍니다: 경로가 빌드 시 정적으로 렌더링될 때와 정적 경로를 처음 방문할 때의 동작입니다.

Next.js의 네 가지 메커니즘에 대한 기본 캐싱 동작을 보여주는 다이어그램으로, 빌드 시와 경로를 처음 방문할 때의 HIT, MISS, SET를 표시합니다.

캐싱 동작은 경로가 정적으로 렌더링되는지 동적으로 렌더링되는지, 데이터가 캐시되었는지 캐시되지 않았는지, 그리고 요청이 초기 방문의 일부인지 후속 탐색의 일부인지에 따라 달라집니다. 사용 사례에 따라 개별 경로와 데이터 요청에 대한 캐싱 동작을 구성할 수 있습니다.

요청 메모이제이션

Next.js는 fetch API를 확장하여 동일한 URL과 옵션을 가진 요청을 자동으로 메모이제이션합니다. 이는 React 컴포넌트 트리의 여러 위치에서 동일한 데이터에 대한 fetch 함수를 호출할 수 있지만 한 번만 실행한다는 것을 의미합니다.

중복 제거된 Fetch 요청

예를 들어, 경로 전체에서 동일한 데이터를 사용해야 하는 경우(예: 레이아웃, 페이지 및 여러 컴포넌트에서), 트리 상단에서 데이터를 가져와 컴포넌트 간에 props를 전달할 필요가 없습니다. 대신, 동일한 데이터에 대해 네트워크를 통해 여러 요청을 하는 성능 영향을 걱정하지 않고 필요한 컴포넌트에서 데이터를 가져올 수 있습니다.

app/example.tsx
async function getItem() {
  // `fetch` 함수는 자동으로 메모이제이션되고 결과가
  // 캐시됩니다
  const res = await fetch("https://.../item/1");
  return res.json();
}
 
// 이 함수는 두 번 호출되지만 처음에만 실행됩니다
const item = await getItem(); // 캐시 MISS
 
// 두 번째 호출은 경로의 어디에서나 있을 수 있습니다
const item = await getItem(); // 캐시 HIT
app/example.js
async function getItem() {
  // `fetch` 함수는 자동으로 메모이제이션되고 결과가
  // 캐시됩니다
  const res = await fetch("https://.../item/1");
  return res.json();
}
 
// 이 함수는 두 번 호출되지만 처음에만 실행됩니다
const item = await getItem(); // 캐시 MISS
 
// 두 번째 호출은 경로의 어디에서나 있을 수 있습니다
const item = await getItem(); // 캐시 HIT

요청 메모이제이션의 작동 방식

React 렌더링 중 fetch 메모이제이션이 작동하는 방식을 보여주는 다이어그램.
  • 경로를 렌더링하는 동안, 특정 요청이 처음 호출될 때 그 결과는 메모리에 없으므로 캐시 MISS가 됩니다.
  • 따라서 함수가 실행되고, 외부 소스에서 데이터가 가져와지며, 결과는 메모리에 저장됩니다.
  • 동일한 렌더링 패스에서 해당 요청의 후속 함수 호출은 캐시 HIT가 되며, 함수를 실행하지 않고 메모리에서 데이터가 반환됩니다.
  • 경로가 렌더링되고 렌더링 패스가 완료되면 메모리가 "리셋"되고 모든 요청 메모이제이션 항목이 지워집니다.

알아두면 좋은 점:

  • 요청 메모이제이션은 Next.js 기능이 아닌 React 기능입니다. 여기에 포함된 이유는 다른 캐싱 메커니즘과 어떻게 상호작용하는지 보여주기 위해서입니다.
  • 메모이제이션은 fetch 요청의 GET 메서드에만 적용됩니다.
  • 메모이제이션은 React 컴포넌트 트리에만 적용됩니다. 이는 다음을 의미합니다:
    • generateMetadata, generateStaticParams, 레이아웃, 페이지 및 기타 서버 컴포넌트의 fetch 요청에 적용됩니다.
    • React 컴포넌트 트리의 일부가 아니므로 라우트 핸들러의 fetch 요청에는 적용되지 않습니다.
  • fetch가 적합하지 않은 경우(예: 일부 데이터베이스 클라이언트, CMS 클라이언트 또는 GraphQL 클라이언트), React cache 함수를 사용하여 함수를 메모이제이션할 수 있습니다.

지속 기간

캐시는 React 컴포넌트 트리 렌더링이 완료될 때까지 서버 요청의 수명 동안 지속됩니다.

재검증

메모이제이션은 서버 요청 간에 공유되지 않고 렌더링 중에만 적용되므로 재검증할 필요가 없습니다.

기본 설정에서 제외하기

메모이제이션은 fetch 요청의 GET 메서드에만 적용되며, POSTDELETE와 같은 다른 메서드는 메모이제이션되지 않습니다. 이 기본 동작은 React의 최적화이며 기본 설정에서 제외하지 않는 것을 권장합니다.

개별 요청을 관리하려면 AbortControllersignal 속성을 사용할 수 있습니다. 그러나 이는 요청을 메모이제이션에서 제외하는 것이 아니라 진행 중인 요청을 중단합니다.

app/example.js
const { signal } = new AbortController();
fetch(url, { signal });

데이터 캐시

Next.js에는 들어오는 서버 요청배포 간에 데이터 가져오기 결과를 지속하는 내장 데이터 캐시가 있습니다. 이는 Next.js가 서버의 각 요청이 자체 영구 캐싱 의미를 설정할 수 있도록 네이티브 fetch API를 확장하기 때문에 가능합니다.

알아두면 좋은 점: 브라우저에서 fetchcache 옵션은 요청이 브라우저의 HTTP 캐시와 어떻게 상호작용할지를 나타내는 반면, Next.js에서 cache 옵션은 서버 측 요청이 서버의 데이터 캐시와 어떻게 상호작용할지를 나타냅니다.

기본적으로 fetch를 사용하는 데이터 요청은 캐시되지 않습니다. fetchcachenext.revalidate 옵션을 사용하여 캐싱 동작을 구성할 수 있습니다.

데이터 캐시의 작동 방식

캐시된 및 캐시되지 않은 fetch 요청이 데이터 캐시와 어떻게 상호작용하는지 보여주는 다이어그램. 캐시된 요청은 데이터 캐시에 저장되고 메모이제이션되며, 캐시되지 않은 요청은 데이터 소스에서 가져와 데이터 캐시에 저장되지 않고 메모이제이션됩니다.
  • 렌더링 중에 'force-cache' 옵션이 있는 fetch 요청이 처음 호출될 때, Next.js는 데이터 캐시에서 캐시된 응답을 확인합니다.
  • 캐시된 응답이 발견되면 즉시 반환되고 메모이제이션됩니다.
  • 캐시된 응답이 발견되지 않으면 데이터 소스에 요청이 이루어지고, 결과는 데이터 캐시에 저장되며 메모이제이션됩니다.
  • 캐시되지 않은 데이터(예: cache 옵션이 정의되지 않았거나 { cache: 'no-store' }를 사용하는 경우)의 경우, 결과는 항상 데이터 소스에서 가져와지고 메모이제이션됩니다.
  • 데이터가 캐시되었는지 여부에 관계없이 요청은 항상 메모이제이션되어 React 렌더링 과정 동안 동일한 데이터에 대한 중복 요청을 피합니다.

데이터 캐시와 요청 메모이제이션의 차이점

두 캐싱 메커니즘 모두 캐시된 데이터를 재사용하여 성능을 향상시키지만, 데이터 캐시는 들어오는 요청과 배포 간에 지속되는 반면 메모이제이션은 요청의 수명 동안만 지속됩니다.

메모이제이션을 통해 우리는 동일한 렌더링 패스에서 렌더링 서버에서 데이터 캐시 서버(예: CDN 또는 엣지 네트워크) 또는 데이터 소스(예: 데이터베이스 또는 CMS)로 네트워크 경계를 넘어야 하는 중복 요청의 수를 줄입니다. 데이터 캐시를 통해 우리는 원본 데이터 소스에 대한 요청 수를 줄입니다.

지속 기간

데이터 캐시는 재검증하거나 기본 설정에서 제외하지 않는 한 들어오는 요청과 배포 간에 지속됩니다.

재검증

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

  • 시간 기반 재검증: 일정 시간이 지나고 새로운 요청이 이루어진 후 데이터를 재검증합니다. 이는 자주 변경되지 않고 최신 상태가 그다지 중요하지 않은 데이터에 유용합니다.
  • 온디맨드 재검증: 이벤트(예: 폼 제출)를 기반으로 데이터를 재검증합니다. 온디맨드 재검증은 태그 기반 또는 경로 기반 접근 방식을 사용하여 한 번에 데이터 그룹을 재검증할 수 있습니다. 이는 최신 데이터를 가능한 한 빨리 표시하고 싶을 때 유용합니다(예: 헤드리스 CMS의 콘텐츠가 업데이트되었을 때).

시간 기반 재검증

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

// 최대 1시간마다 재검증
fetch("https://...", { next: { revalidate: 3600 } });

또는 라우트 세그먼트 구성 옵션을 사용하여 세그먼트의 모든 fetch 요청을 구성하거나 fetch를 사용할 수 없는 경우에 사용할 수 있습니다.

시간 기반 재검증의 작동 방식

시간 기반 재검증이 작동하는 방식을 보여주는 다이어그램. 재검증 기간 이후, 첫 번째 요청에 대해 오래된 데이터가 반환되고, 그 후 데이터가 재검증됩니다.
  • revalidate가 있는 fetch 요청이 처음 호출될 때, 데이터는 외부 데이터 소스에서 가져와져 데이터 캐시에 저장됩니다.
  • 지정된 시간 프레임(예: 60초) 내에 호출되는 모든 요청은 캐시된 데이터를 반환합니다.
  • 시간 프레임이 지난 후, 다음 요청은 여전히 캐시된(이제는 오래된) 데이터를 반환합니다.
    • Next.js는 백그라운드에서 데이터 재검증을 트리거합니다.
    • 데이터가 성공적으로 가져와지면 Next.js는 데이터 캐시를 새로운 데이터로 업데이트합니다.
    • 백그라운드 재검증이 실패하면 이전 데이터는 변경되지 않은 채로 유지됩니다.

이는 stale-while-revalidate 동작과 유사합니다.

온디맨드 재검증

데이터는 경로(revalidatePath)별로 또는 캐시 태그(revalidateTag)별로 온디맨드로 재검증할 수 있습니다.

온디맨드 재검증의 작동 방식

온디맨드 재검증이 작동하는 방식을 보여주는 다이어그램. 재검증 요청 후 데이터 캐시가 새로운 데이터로 업데이트됩니다.
  • fetch 요청이 처음 호출될 때, 데이터는 외부 데이터 소스에서 가져와져 데이터 캐시에 저장됩니다.
  • 온디맨드 재검증이 트리거되면 적절한 캐시 항목이 캐시에서 제거됩니다.
    • 이는 시간 기반 재검증과 다릅니다. 시간 기반 재검증은 새로운 데이터를 가져올 때까지 오래된 데이터를 캐시에 유지합니다.
  • 다음 요청 시, 다시 캐시 MISS가 되어 데이터는 외부 데이터 소스에서 가져와져 데이터 캐시에 저장됩니다.

기본 설정에서 제외하기

fetch 요청은 기본적으로 캐시되지 않으므로 캐싱을 기본 설정에서 제외할 필요가 없습니다. 이는 fetch가 호출될 때마다 데이터 소스에서 데이터를 가져온다는 의미입니다.

참고: 데이터 캐시는 현재 레이아웃, 페이지 및 라우트 핸들러에서만 사용할 수 있으며 미들웨어에서는 사용할 수 없습니다. 미들웨어 내에서 수행되는 모든 가져오기는 기본적으로 캐시되지 않습니다.

Vercel 데이터 캐시

Next.js 애플리케이션이 Vercel에 배포된 경우, Vercel 데이터 캐시 문서를 읽어 Vercel 특정 기능을 더 잘 이해하는 것이 좋습니다.

전체 경로 캐시

관련 용어:

자동 정적 최적화, 정적 사이트 생성, 또는 정적 렌더링이라는 용어가 애플리케이션의 경로를 빌드 시에 렌더링하고 캐싱하는 과정을 지칭하는 데 서로 바꿔 사용되는 것을 볼 수 있습니다.

Next.js는 자동으로 빌드 시에 경로를 렌더링하고 캐시합니다. 이는 모든 요청에 대해 서버에서 렌더링하는 대신 캐시된 경로를 제공할 수 있게 하는 최적화로, 더 빠른 페이지 로드를 가능하게 합니다.

전체 경로 캐시가 어떻게 작동하는지 이해하려면 React가 렌더링을 어떻게 처리하는지, 그리고 Next.js가 결과를 어떻게 캐시하는지 살펴보는 것이 도움이 됩니다:

1. 서버에서의 React 렌더링

서버에서 Next.js는 React의 API를 사용하여 렌더링을 조율합니다. 렌더링 작업은 개별 경로 세그먼트와 Suspense 경계에 의해 청크로 나뉩니다.

각 청크는 두 단계로 렌더링됩니다:

  1. React는 서버 컴포넌트를 React 서버 컴포넌트 페이로드라고 하는 스트리밍에 최적화된 특별한 데이터 형식으로 렌더링합니다.
  2. Next.js는 React 서버 컴포넌트 페이로드와 클라이언트 컴포넌트 JavaScript 지침을 사용하여 서버에서 HTML을 렌더링합니다.

이는 모든 것이 렌더링될 때까지 기다릴 필요 없이 작업이 완료되는 대로 응답을 스트리밍할 수 있다는 것을 의미합니다.

React 서버 컴포넌트 페이로드란 무엇인가요?

React 서버 컴포넌트 페이로드는 렌더링된 React 서버 컴포넌트 트리의 컴팩트한 바이너리 표현입니다. React가 클라이언트에서 브라우저의 DOM을 업데이트하는 데 사용됩니다. React 서버 컴포넌트 페이로드에는 다음이 포함됩니다:

  • 서버 컴포넌트의 렌더링 결과
  • 클라이언트 컴포넌트가 렌더링되어야 할 위치에 대한 플레이스홀더와 그들의 JavaScript 파일에 대한 참조
  • 서버 컴포넌트에서 클라이언트 컴포넌트로 전달된 모든 props

자세한 내용은 서버 컴포넌트 문서를 참조하세요.

2. 서버에서의 Next.js 캐싱 (전체 경로 캐시)

정적으로 렌더링된 경로에 대해 React 서버 컴포넌트 페이로드와 HTML이 서버에서 캐시되는 전체 경로 캐시의 기본 동작을 보여줍니다.

Next.js의 기본 동작은 서버에서 경로의 렌더링 결과(React 서버 컴포넌트 페이로드 및 HTML)를 캐시하는 것입니다. 이는 빌드 시 또는 재검증 중 정적으로 렌더링된 경로에 적용됩니다.

3. 클라이언트에서의 React 하이드레이션 및 조정

요청 시, 클라이언트에서:

  1. HTML은 클라이언트 및 서버 컴포넌트의 빠른 비대화형 초기 미리보기를 즉시 표시하는 데 사용됩니다.
  2. React 서버 컴포넌트 페이로드는 클라이언트 및 렌더링된 서버 컴포넌트 트리를 조정하고 DOM을 업데이트하는 데 사용됩니다.
  3. JavaScript 지침은 클라이언트 컴포넌트를 하이드레이트하고 애플리케이션을 대화형으로 만드는 데 사용됩니다.

4. 클라이언트에서의 Next.js 캐싱 (라우터 캐시)

React 서버 컴포넌트 페이로드는 클라이언트 측 라우터 캐시에 저장됩니다 - 개별 경로 세그먼트로 분할된 별도의 인메모리 캐시입니다. 이 라우터 캐시는 이전에 방문한 경로를 저장하고 미래의 경로를 미리 가져옴으로써 탐색 경험을 개선하는 데 사용됩니다.

5. 후속 탐색

후속 탐색이나 미리 가져오기 중에 Next.js는 React 서버 컴포넌트 페이로드가 라우터 캐시에 저장되어 있는지 확인합니다. 그렇다면 서버에 새 요청을 보내지 않습니다.

만약 경로 세그먼트가 캐시에 없다면, Next.js는 서버에서 React 서버 컴포넌트 페이로드를 가져와 클라이언트의 라우터 캐시를 채웁니다.

정적 및 동적 렌더링

경로가 빌드 시에 캐시되는지 여부는 정적으로 렌더링되는지 동적으로 렌더링되는지에 따라 달라집니다. 정적 경로는 기본적으로 캐시되는 반면, 동적 경로는 요청 시 렌더링되며 캐시되지 않습니다.

이 다이어그램은 캐시된 데이터와 캐시되지 않은 데이터를 가진 정적 및 동적으로 렌더링된 경로 간의 차이를 보여줍니다:

정적 및 동적 렌더링이 전체 경로 캐시에 미치는 영향. 정적 경로는 빌드 시 또는 데이터 재검증 후에 캐시되는 반면, 동적 경로는 절대 캐시되지 않습니다

정적 및 동적 렌더링에 대해 자세히 알아보세요.

지속 기간

기본적으로 전체 경로 캐시는 지속적입니다. 이는 렌더링 출력이 사용자 요청 간에 캐시된다는 것을 의미합니다.

무효화

전체 경로 캐시를 무효화할 수 있는 두 가지 방법이 있습니다:

  • 데이터 재검증: 데이터 캐시를 재검증하면 서버에서 컴포넌트를 다시 렌더링하고 새로운 렌더링 출력을 캐싱함으로써 라우터 캐시를 차례로 무효화합니다.
  • 재배포: 데이터 캐시와 달리 배포 간에 지속되는 전체 경로 캐시는 새로운 배포 시 지워집니다.

기본 설정에서 제외하기

다음과 같은 방법으로 전체 경로 캐시를 기본 설정에서 제외하거나, 다시 말해 들어오는 모든 요청에 대해 컴포넌트를 동적으로 렌더링할 수 있습니다:

  • 동적 함수 사용: 이는 전체 경로 캐시에서 경로를 기본 설정에서 제외하고 요청 시 동적으로 렌더링합니다. 데이터 캐시는 여전히 사용될 수 있습니다.
  • dynamic = 'force-dynamic' 또는 revalidate = 0 경로 세그먼트 구성 옵션 사용: 이는 전체 경로 캐시와 데이터 캐시를 건너뜁니다. 즉, 컴포넌트가 렌더링되고 서버로 들어오는 모든 요청에 대해 데이터가 가져와집니다. 라우터 캐시는 클라이언트 측 캐시이므로 여전히 적용됩니다.
  • 데이터 캐시 해제: 경로에 캐시되지 않은 fetch 요청이 있으면 이는 전체 경로 캐시에서 경로를 기본 설정에서 제외합니다. 특정 fetch 요청에 대한 데이터는 들어오는 모든 요청에 대해 가져와집니다. 캐싱을 기본 설정에서 제외하지 않은 다른 fetch 요청은 여전히 데이터 캐시에 캐시됩니다. 이를 통해 캐시된 데이터와 캐시되지 않은 데이터의 하이브리드가 가능합니다.

클라이언트 측 라우터 캐시

Next.js에는 레이아웃, 로딩 상태 및 페이지로 분할된 경로 세그먼트의 RSC 페이로드를 저장하는 인메모리 클라이언트 측 라우터 캐시가 있습니다.

사용자가 경로 간을 탐색할 때 Next.js는 방문한 경로 세그먼트를 캐시하고 사용자가 탐색할 가능성이 높은 경로를 미리 가져옵니다. 이는 즉각적인 뒤로/앞으로 탐색, 탐색 간 전체 페이지 리로드 없음, React 상태 및 브라우저 상태 보존을 가능하게 합니다.

라우터 캐시를 사용하면:

  • 레이아웃은 탐색 시 캐시되고 재사용됩니다(부분 렌더링).
  • 로딩 상태즉각적인 로딩 상태를 위해 탐색 시 캐시되고 재사용됩니다.
  • 페이지는 기본적으로 캐시되지 않지만 브라우저의 뒤로 및 앞으로 탐색 중에 재사용됩니다. 실험적인 staleTimes 구성 옵션을 사용하여 페이지 세그먼트에 대한 캐싱을 활성화할 수 있습니다.

알아두면 좋은 점: 이 캐시는 특별히 Next.js와 서버 컴포넌트에 적용되며, 브라우저의 bfcache와는 다르지만 유사한 결과를 가집니다.

지속 기간

캐시는 브라우저의 임시 메모리에 저장됩니다. 라우터 캐시가 지속되는 기간을 결정하는 두 가지 요소가 있습니다:

  • 세션: 캐시는 탐색 간에 지속됩니다. 그러나 페이지 새로고침 시 지워집니다.
  • 자동 무효화 기간: 레이아웃 및 로딩 상태의 캐시는 특정 시간 후에 자동으로 무효화됩니다. 기간은 리소스가 어떻게 미리 가져와졌는지와 리소스가 정적으로 생성되었는지에 따라 달라집니다:
    • 기본 미리 가져오기 (prefetch={null} 또는 지정되지 않음): 동적 페이지의 경우 캐시되지 않음, 정적 페이지의 경우 5분
    • 전체 미리 가져오기 (prefetch={true} 또는 router.prefetch): 정적 및 동적 페이지 모두 5분

페이지 새로고침은 모든 캐시된 세그먼트를 지우는 반면, 자동 무효화 기간은 미리 가져온 시점부터 개별 세그먼트에만 영향을 미칩니다.

알아두면 좋은 점: 실험적인 staleTimes 구성 옵션을 사용하여 위에서 언급한 자동 무효화 시간을 조정할 수 있습니다.

무효화

라우터 캐시를 무효화할 수 있는 두 가지 방법이 있습니다:

  • 서버 액션에서:
    • (revalidatePath)로 경로별 또는 (revalidateTag)로 캐시 태그별로 데이터를 온디맨드로 재검증
    • cookies.set 또는 cookies.delete 사용은 쿠키를 사용하는 경로가 오래되는 것을 방지하기 위해 라우터 캐시를 무효화합니다(예: 인증).
  • router.refresh를 호출하면 라우터 캐시를 무효화하고 현재 경로에 대해 서버에 새로운 요청을 합니다.

기본 설정에서 제외하기

Next.js 15부터 페이지 세그먼트는 기본적으로 설정에서 제외됩니다.

알아두면 좋은 점: <Link> 컴포넌트의 prefetch prop을 false로 설정하여 미리 가져오기를 기본설정에서 제외 할 수도 있습니다.

캐시 상호작용

다양한 캐싱 메커니즘을 구성할 때 그들이 서로 어떻게 상호작용하는지 이해하는 것이 중요합니다:

데이터 캐시와 전체 경로 캐시

  • 데이터 캐시를 재검증하거나 설정에서 제외하면 전체 경로 캐시가 무효화됩니다. 렌더링 출력이 데이터에 의존하기 때문입니다.
  • 전체 경로 캐시를 무효화하거나 설정에서 제외해도 데이터 캐시에는 영향을 미치지 않습니다. 캐시된 데이터와 캐시되지 않은 데이터를 모두 가진 경로를 동적으로 렌더링할 수 있습니다. 이는 페이지의 대부분이 캐시된 데이터를 사용하지만 요청 시 가져와야 하는 데이터에 의존하는 몇 개의 컴포넌트가 있을 때 유용합니다. 모든 데이터를 다시 가져오는 성능 영향을 걱정하지 않고 동적으로 렌더링할 수 있습니다.

데이터 캐시와 클라이언트 측 라우터 캐시

  • 데이터 캐시와 라우터 캐시를 즉시 무효화하려면 서버 액션에서 revalidatePath 또는 revalidateTag를 사용할 수 있습니다.
  • 라우트 핸들러에서 데이터 캐시를 재검증하는 것은 라우트 핸들러가 특정 경로에 묶여있지 않기 때문에 라우터 캐시를 즉시 무효화하지 않습니다. 이는 하드 새로고침이나 자동 무효화 기간이 경과할 때까지 라우터 캐시가 이전 페이로드를 계속 제공한다는 것을 의미합니다.

API

다음 표는 다양한 Next.js API가 캐싱에 미치는 영향을 개괄적으로 보여줍니다:

API라우터 캐시전체 경로 캐시데이터 캐시React 캐시
<Link prefetch>캐시
router.prefetch캐시
router.refresh재검증
fetch캐시캐시
fetch options.cache캐시 또는 설정에서 제외
fetch options.next.revalidate재검증재검증
fetch options.next.tags캐시캐시
revalidateTag재검증 (서버 액션)재검증재검증
revalidatePath재검증 (서버 액션)재검증재검증
const revalidate재검증 또는 설정에서 제외재검증 또는 설정에서 제외
const dynamic캐시 또는 설정에서 제외캐시 또는 설정에서 제외
cookies재검증 (서버 액션)설정에서 제외
headers, searchParams설정에서 제외
generateStaticParams캐시
React.cache캐시
unstable_cache캐시

기본적으로 <Link> 컴포넌트는 전체 경로 캐시에서 경로를 자동으로 미리 가져와 React 서버 컴포넌트 페이로드를 라우터 캐시에 추가합니다.

미리 가져오기를 비활성화하려면 prefetch prop을 false로 설정할 수 있습니다. 하지만 이는 캐시를 영구적으로 건너뛰지 않으며, 사용자가 경로를 방문할 때 경로 세그먼트는 여전히 클라이언트 측에서 캐시됩니다.

<Link> 컴포넌트에 대해 자세히 알아보세요.

router.prefetch

useRouter 훅의 prefetch 옵션을 사용하여 수동으로 경로를 미리 가져올 수 있습니다. 이는 React 서버 컴포넌트 페이로드를 라우터 캐시에 추가합니다.

useRouter API 참조를 참조하세요.

router.refresh

useRouter 훅의 refresh 옵션을 사용하여 수동으로 경로를 새로 고칠 수 있습니다. 이는 라우터 캐시를 완전히 지우고 현재 경로에 대해 서버에 새로운 요청을 합니다. refresh는 데이터 캐시나 전체 경로 캐시에 영향을 미치지 않습니다.

렌더링된 결과는 React 상태와 브라우저 상태를 보존하면서 클라이언트에서 조정됩니다.

useRouter API 참조를 참조하세요.

fetch

fetch에서 반환된 데이터는 데이터 캐시에 자동으로 캐시되지 않습니다.

// 기본적으로 캐시됩니다. `no-store`는 기본 옵션이며 생략할 수 있습니다.
fetch(`https://...`, { cache: "no-store" });

렌더링 출력이 데이터에 의존하기 때문에 cache: 'no-store'를 사용하면 fetch 요청이 사용되는 경로에 대한 전체 경로 캐시도 건너뜁니다. 즉, 경로는 모든 요청에 대해 동적으로 렌더링되지만 동일한 경로에서 다른 캐시된 데이터 요청을 계속 가질 수 있습니다.

더 많은 옵션은 fetch API 참조를 참조하세요.

fetch options.cache

개별 fetchcache 옵션을 force-cache로 설정하여 캐싱에 활성화 할 수 있습니다:

// 캐싱 활성화
fetch(`https://...`, { cache: "force-cache" });

더 많은 옵션은 fetch API 참조를 참조하세요.

fetch options.next.revalidate

fetchnext.revalidate 옵션을 사용하여 개별 fetch 요청의 재검증 기간(초 단위)을 설정할 수 있습니다. 이는 데이터 캐시를 재검증하고, 차례로 전체 경로 캐시를 재검증합니다. 새로운 데이터가 가져와지고 서버에서 컴포넌트가 다시 렌더링됩니다.

// 최대 1시간 후에 재검증
fetch(`https://...`, { next: { revalidate: 3600 } });

더 많은 옵션은 fetch API 참조를 참조하세요.

fetch options.next.tagsrevalidateTag

Next.js에는 세분화된 데이터 캐싱 및 재검증을 위한 캐시 태깅 시스템이 있습니다.

  1. fetch 또는 unstable_cache를 사용할 때 하나 이상의 태그로 캐시 항목에 태그를 지정할 수 있습니다.
  2. 그런 다음 revalidateTag를 호출하여 해당 태그와 관련된 캐시 항목을 제거할 수 있습니다.

예를 들어, 데이터를 가져올 때 태그를 설정할 수 있습니다:

// 태그로 데이터 캐시
fetch(`https://...`, { next: { tags: ["a", "b", "c"] } });

그런 다음 revalidateTag를 특정 태그와 함께 호출하여 캐시 항목을 제거합니다:

// 특정 태그를 가진 항목 재검증
revalidateTag("a");

revalidateTag를 사용할 수 있는 두 가지 위치가 있으며, 목표에 따라 선택할 수 있습니다:

  1. 라우트 핸들러 - 서드파티 이벤트(예: 웹훅)에 대한 응답으로 데이터를 재검증합니다. 이는 라우트 핸들러가 특정 경로에 묶여있지 않기 때문에 라우터 캐시를 즉시 무효화하지 않습니다.
  2. 서버 액션 - 사용자 액션(예: 폼 제출) 후 데이터를 재검증합니다. 이는 관련 경로에 대한 라우터 캐시를 무효화합니다.

revalidatePath

revalidatePath를 사용하면 특정 경로 아래의 경로 세그먼트를 수동으로 재검증하고 단일 작업으로 다시 렌더링할 수 있습니다. revalidatePath 메서드를 호출하면 데이터 캐시를 재검증하고, 차례로 전체 경로 캐시를 무효화합니다.

revalidatePath("/");

revalidatePath를 사용할 수 있는 두 가지 위치가 있으며, 목표에 따라 선택할 수 있습니다:

  1. 라우트 핸들러 - 서드파티 이벤트(예: 웹훅)에 대한 응답으로 데이터를 재검증합니다.
  2. 서버 액션 - 사용자 상호작용(예: 폼 제출, 버튼 클릭) 후 데이터를 재검증합니다.

자세한 정보는 revalidatePath API 참조를 참조하세요.

revalidatePath vs. router.refresh:

router.refresh를 호출하면 라우터 캐시가 지워지고 데이터 캐시나 전체 경로 캐시를 무효화하지 않고 서버에서 경로 세그먼트를 다시 렌더링합니다.

차이점은 revalidatePath는 데이터 캐시와 전체 경로 캐시를 제거하는 반면, router.refresh()는 클라이언트 측 API이기 때문에 데이터 캐시와 전체 경로 캐시를 변경하지 않는다는 것입니다.

동적 함수

cookiesheaders 같은 동적 함수와 페이지의 searchParams prop은 런타임 들어오는 요청 정보에 의존합니다. 이들을 사용하면 경로가 전체 경로 캐시에서 제외됩니다. 다시 말해, 경로가 동적으로 렌더링됩니다.

cookies

서버 액션에서 cookies.set 또는 cookies.delete를 사용하면 쿠키를 사용하는 경로가 오래되는 것을 방지하기 위해 라우터 캐시가 무효화됩니다(예: 인증 변경 사항을 반영하기 위해).

cookies API 참조를 참조하세요.

세그먼트 구성 옵션

라우트 세그먼트 구성 옵션은 경로 세그먼트 기본값을 재정의하거나 fetch API를 사용할 수 없을 때(예: 데이터베이스 클라이언트 또는 서드파티 라이브러리) 사용할 수 있습니다.

다음 라우트 세그먼트 구성 옵션은 데이터 캐시와 전체 경로 캐시에서 제외합니다:

  • const dynamic = 'force-dynamic'
  • const revalidate = 0

더 많은 옵션은 라우트 세그먼트 구성 문서를 참조하세요.

generateStaticParams

동적 세그먼트(예: app/blog/[slug]/page.js)의 경우, generateStaticParams에 의해 제공된 경로는 빌드 시 전체 경로 캐시에 캐시됩니다. 요청 시, Next.js는 빌드 시 알려지지 않았던 경로도 처음 방문할 때 캐시합니다.

빌드 시 모든 경로를 정적으로 렌더링하려면 generateStaticParams에 전체 경로 목록을 제공하세요:

app/blog/[slug]/page.js
export async function generateStaticParams() {
  const posts = await fetch("https://.../posts").then((res) => res.json());
 
  return posts.map((post) => ({
    slug: post.slug,
  }));
}

빌드 시 경로의 일부를 정적으로 렌더링하고 나머지는 런타임에 처음 방문할 때 렌더링하려면 부분적인 경로 목록을 반환하세요:

app/blog/[slug]/page.js
export async function generateStaticParams() {
  const posts = await fetch("https://.../posts").then((res) => res.json());
 
  // 빌드 시 처음 10개의 게시물 렌더링
  return posts.slice(0, 10).map((post) => ({
    slug: post.slug,
  }));
}

모든 경로를 처음 방문할 때 정적으로 렌더링하려면 빈 배열을 반환하거나(빌드 시 렌더링되는 경로 없음) export const dynamic = 'force-static'을 사용하세요:

app/blog/[slug]/page.js
export async function generateStaticParams() {
  return [];
}

알아두면 좋은 점: 빈 배열이라도 generateStaticParams에서 배열을 반환해야 합니다. 그렇지 않으면 경로가 동적으로 렌더링됩니다.

app/changelog/[slug]/page.js
export const dynamic = "force-static";

요청 시 캐싱을 비활성화하려면 경로 세그먼트에 export const dynamicParams = false 옵션을 추가하세요. 이 구성 옵션을 사용하면 generateStaticParams에서 제공한 경로만 제공되며, 다른 경로는 404가 되거나 일치합니다(catch-all 경로의 경우).

React cache 함수

React cache 함수를 사용하면 함수의 반환 값을 메모이제이션할 수 있어, 동일한 함수를 여러 번 호출하면서도 한 번만 실행할 수 있습니다.

fetch 요청은 자동으로 메모이제이션되므로 React cache로 감쌀 필요가 없습니다. 하지만 fetch API가 적합하지 않은 사용 사례에 대해 데이터 요청을 수동으로 메모이제이션하기 위해 cache를 사용할 수 있습니다. 예를 들어, 일부 데이터베이스 클라이언트, 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;
});
utils/get-item.js
import { cache } from "react";
import db from "@/lib/db";
 
export const getItem = cache(async (id) => {
  const item = await db.item.findUnique({ id });
  return item;
});