(준)공식 문서/Next.js

[ Next.js 15 공식 문서 ] Caching (캐싱)

Je-chan 2025. 7. 27. 17:54

Next.js에서의 캐싱

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

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

개요

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

메커니즘 대상 위치 목적 지속 시간
Request Memoization 함수의 반환 값 서버 React 컴포넌트 트리에서 데이터 재사용 요청별 생명주기
Data Cache 데이터 서버 사용자 요청과 배포 간 데이터 저장 지속적 (재검증 가능)
Full Route Cache HTML 및 RSC 페이로드 서버 렌더링 비용 절감 및 성능 향상 지속적 (재검증 가능)
Router Cache RSC 페이로드 클라이언트 네비게이션 시 서버 요청 감소 사용자 세션 또는 시간 기반

기본적으로 Next.js는 성능을 향상시키고 비용을 절감하기 위해 가능한 한 많이 캐싱합니다. 이는 라우트가 정적으로 렌더링되고 데이터 요청이 옵트아웃하지 않는 한 캐싱됨을 의미합니다. 아래 다이어그램은 기본 캐싱 동작을 보여줍니다: 라우트가 빌드 시간에 정적으로 렌더링될 때와 정적 라우트가 처음 방문될 때입니다.

CLIENT
Router Cache
RSC Payload
SERVER
Full Route Cache
Request Memoization
Data Cache

기본 캐싱 동작: 빌드 타임과 첫 방문 시의 HIT, MISS, SET

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

중요한 정보
Fetch 캐싱은 middleware에서 지원되지 않습니다. middleware 내부에서 수행되는 모든 fetch는 캐시되지 않습니다.

Request Memoization

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

중복 제거된 Fetch 요청
RootLayout
├ Component A
└ Page
All Requests
Request A
Request B
Request B
Request C
Memoized
Request A
Request B
Request C

예를 들어, 라우트 전반에 걸쳐 동일한 데이터를 사용해야 하는 경우(예: Layout, Page, 여러 컴포넌트에서), 트리의 최상단에서 데이터를 fetch하고 컴포넌트 간에 props를 전달할 필요가 없습니다. 대신, 동일한 데이터에 대해 네트워크를 통해 여러 요청을 만드는 성능 영향을 걱정하지 않고 필요한 컴포넌트에서 데이터를 fetch할 수 있습니다.

async function getItem() {
  // `fetch` 함수는 자동으로 메모이제이션되고 결과가 캐시됩니다
  const res = await fetch('https://.../item/1')
  return res.json()
}

// 이 함수는 두 번 호출되지만 첫 번째만 실행됩니다
const item = await getItem() // cache MISS

// 두 번째 호출은 라우트 어디든 있을 수 있습니다
const item = await getItem() // cache HIT

Request Memoization 작동 방식

React 렌더링 중 fetch 메모이제이션 작동 방식
SINGLE RENDER PASS
fetch('...item/1')
MISS → SET
fetch('...item/1')
HIT
  • 라우트를 렌더링하는 동안, 특정 요청이 처음 호출되면 그 결과가 메모리에 없으므로 캐시 MISS가 됩니다.
  • 따라서 함수가 실행되고, 외부 소스에서 데이터를 가져와서 결과가 메모리에 저장됩니다.
  • 동일한 렌더 패스에서 해당 요청의 후속 함수 호출은 캐시 HIT이 되고, 함수를 실행하지 않고 메모리에서 데이터가 반환됩니다.
  • 라우트가 렌더링되고 렌더링 패스가 완료되면, 메모리가 "리셋"되고 모든 요청 메모이제이션 항목이 삭제됩니다.
알아두면 좋은 점
  • Request memoization은 React 기능이지 Next.js 기능이 아닙니다. 다른 캐싱 메커니즘과 어떻게 상호작용하는지 보여주기 위해 여기에 포함되었습니다.
  • 메모이제이션은 fetch 요청의 GET 메서드에만 적용됩니다.
  • 메모이제이션은 React 컴포넌트 트리에만 적용됩니다. 이는 다음을 의미합니다:
    • generateMetadata, generateStaticParams, Layouts, Pages, 기타 Server Components의 fetch 요청에 적용됩니다.
    • Route Handlers의 fetch 요청에는 적용되지 않습니다. React 컴포넌트 트리의 일부가 아니기 때문입니다.
  • fetch가 적합하지 않은 경우(예: 일부 데이터베이스 클라이언트, CMS 클라이언트, GraphQL 클라이언트), React cache 함수를 사용하여 함수를 메모이제이션할 수 있습니다.

지속 시간

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

재검증

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

옵트아웃

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

개별 요청을 관리하려면 AbortControllersignal 속성을 사용할 수 있습니다.

const { signal } = new AbortController()
fetch(url, { signal })

Data Cache

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

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

fetchcachenext.revalidate 옵션을 사용하여 캐싱 동작을 구성할 수 있습니다.

개발 모드에서 fetch 데이터는 Hot Module Replacement (HMR)를 위해 재사용되며, 하드 새로고침에 대해서는 캐싱 옵션이 무시됩니다.

Data Cache 작동 방식

캐시된 fetch 요청과 캐시되지 않은 fetch 요청이 Data Cache와 상호작용하는 방식
RENDER
fetch('', { cache: 'force-cache' })
MISS
SET
HIT
RENDER
fetch('', { cache: 'no-store' })
MISS
SKIP
HIT
  • 렌더링 중에 'force-cache' 옵션을 가진 fetch 요청이 처음 호출되면, Next.js는 Data Cache에서 캐시된 응답을 확인합니다.
  • 캐시된 응답이 발견되면 즉시 반환되고 메모이제이션됩니다.
  • 캐시된 응답이 발견되지 않으면, 데이터 소스에 요청을 보내고 결과를 Data Cache에 저장하고 메모이제이션합니다.
  • 캐시되지 않은 데이터(예: cache 옵션이 정의되지 않았거나 { cache: 'no-store' } 사용)의 경우, 결과는 항상 데이터 소스에서 가져와서 메모이제이션됩니다.
  • 데이터가 캐시되든 캐시되지 않든, React 렌더 패스 중에 동일한 데이터에 대한 중복 요청을 피하기 위해 요청은 항상 메모이제이션됩니다.
Data Cache와 Request Memoization의 차이점
두 캐싱 메커니즘 모두 캐시된 데이터를 재사용하여 성능 향상에 도움이 되지만, Data Cache는 들어오는 요청과 배포 간에 지속되는 반면, 메모이제이션은 요청의 수명 동안만 지속됩니다.

지속 시간

Data Cache는 재검증하거나 옵트아웃하지 않는 한 들어오는 요청과 배포 간에 지속됩니다.

재검증

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

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

시간 기반 재검증

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

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

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

시간 기반 재검증 작동 방식

시간 기반 재검증 작동 방식
UNCACHED REQUEST
fetch('...', { next: { revalidate: 60 } })
MISS
HIT

SET 후 60초 미만
CACHED REQUEST
fetch('...', { next: { revalidate: 60 } })
HIT

SET 후 60초 초과
STALE REQUEST
fetch('...', { next: { revalidate: 60 } })
STALE
Revalidate
HIT
  • revalidate가 포함된 fetch 요청이 처음 호출되면, 외부 데이터 소스에서 데이터를 가져와 Data Cache에 저장됩니다.
  • 지정된 시간 프레임(예: 60초) 내에 호출되는 모든 요청은 캐시된 데이터를 반환합니다.
  • 시간 프레임 후에는 다음 요청이 여전히 캐시된(현재는 stale한) 데이터를 반환합니다.
    • Next.js는 백그라운드에서 데이터 재검증을 트리거합니다.
    • 데이터가 성공적으로 가져와지면, Next.js는 새로운 데이터로 Data Cache를 업데이트합니다.
    • 백그라운드 재검증이 실패하면, 이전 데이터가 변경되지 않고 유지됩니다.

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

온디맨드 재검증

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

온디맨드 재검증 작동 방식

온디맨드 재검증 작동 방식
INITIAL REQUEST
fetch('...', { next: { tags: ['a'] } })
MISS
HIT

REVALIDATION
revalidateTag('a')
PURGE

NEW REQUEST
fetch('...', { next: { tags: ['a'] } })
MISS
HIT
  • fetch 요청이 처음 호출되면, 외부 데이터 소스에서 데이터를 가져와 Data Cache에 저장됩니다.
  • 온디맨드 재검증이 트리거되면, 적절한 캐시 항목이 캐시에서 제거됩니다.
    • 이는 새로운 데이터를 가져올 때까지 캐시에 stale 데이터를 유지하는 시간 기반 재검증과 다릅니다.
  • 다음 요청이 들어오면, 다시 캐시 MISS가 되고 외부 데이터 소스에서 데이터를 가져와 Data Cache에 저장됩니다.

옵트아웃

fetch의 응답을 캐시하지 않으려면 다음과 같이 할 수 있습니다:

let data = await fetch('https://api.vercel.app/blog', { cache: 'no-store' })

Full Route Cache

관련 용어
Automatic Static Optimization, Static Site Generation, 또는 Static Rendering이라는 용어가 빌드 시간에 애플리케이션의 라우트를 렌더링하고 캐싱하는 프로세스를 가리키기 위해 서로 바꿔 사용되는 것을 볼 수 있습니다.

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

Full Route Cache가 어떻게 작동하는지 이해하려면, React가 렌더링을 처리하는 방법과 Next.js가 결과를 캐싱하는 방법을 살펴보는 것이 도움이 됩니다:

1. 서버에서 React 렌더링

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

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

  1. React는 Server Components를 스트리밍에 최적화된 특별한 데이터 형식인 React Server Component Payload로 렌더링합니다.
  2. Next.js는 React Server Component Payload와 Client Component JavaScript 지시사항을 사용하여 서버에서 HTML을 렌더링합니다.

이는 작업을 캐싱하거나 응답을 보내기 전에 모든 것이 렌더링될 때까지 기다릴 필요가 없음을 의미합니다. 대신, 작업이 완료되면 응답을 스트리밍할 수 있습니다.

React Server Component Payload란 무엇인가요?
React Server Component Payload는 렌더링된 React Server Components 트리의 압축된 바이너리 표현입니다. 클라이언트에서 React가 브라우저의 DOM을 업데이트하는 데 사용됩니다. React Server Component Payload에는 다음이 포함됩니다:
  • Server Components의 렌더링된 결과
  • Client Components가 렌더링되어야 할 위치의 플레이스홀더와 JavaScript 파일에 대한 참조
  • Server Component에서 Client Component로 전달된 모든 props
자세한 내용은 Server Components 문서를 참조하세요.

2. 서버에서 Next.js 캐싱 (Full Route Cache)

Full Route Cache의 기본 동작
BUILD TIME /
REVALIDATION
Full Route Cache
RSC Payload
HTML
Rendering
Data Cache
OR
Data Source

정적으로 렌더링된 라우트에 대해 서버에서 React Server Component Payload와 HTML이 캐시되는 방식

Next.js의 기본 동작은 서버에서 라우트의 렌더링된 결과(React Server Component Payload와 HTML)를 캐싱하는 것입니다. 이는 빌드 시간에 정적으로 렌더링된 라우트나 재검증 중에 적용됩니다.

3. 클라이언트에서 React Hydration과 Reconciliation

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

  1. HTML은 Client와 Server Components의 빠른 비대화형 초기 미리보기를 즉시 표시하는 데 사용됩니다.
  2. React Server Components Payload는 Client와 렌더링된 Server Component 트리를 조정하고 DOM을 업데이트하는 데 사용됩니다.
  3. JavaScript 지시사항은 Client Components를 hydrate하고 애플리케이션을 대화형으로 만드는 데 사용됩니다.

4. 클라이언트에서 Next.js 캐싱 (Router Cache)

React Server Component Payload는 개별 라우트 세그먼트별로 분할된 별도의 인메모리 캐시인 클라이언트 측 Router Cache에 저장됩니다. 이 Router Cache는 이전에 방문한 라우트를 저장하고 미래 라우트를 프리페치하여 네비게이션 경험을 향상시키는 데 사용됩니다.

5. 후속 네비게이션

후속 네비게이션이나 프리페치 중에 Next.js는 React Server Components Payload가 Router Cache에 저장되어 있는지 확인합니다. 있다면 서버에 새 요청을 보내는 것을 건너뜁니다.

라우트 세그먼트가 캐시에 없다면, Next.js는 서버에서 React Server Components Payload를 가져와서 클라이언트의 Router Cache를 채웁니다.

정적 및 동적 렌더링

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

정적 및 동적 렌더링이 Full Route Cache에 미치는 영향
INITIAL VISIT
Static Route
MISS
HIT
SUBSEQUENT NAVIGATION
Static Route
HIT

INITIAL VISIT
Dynamic Route
MISS
SKIP
Fetch
SUBSEQUENT NAVIGATION
Dynamic Route
MISS
SKIP
Fetch

정적 라우트는 빌드 시간이나 데이터 재검증 후에 캐시되지만, 동적 라우트는 절대 캐시되지 않습니다

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

지속 시간

기본적으로 Full Route Cache는 지속적입니다. 이는 렌더 출력이 사용자 요청 간에 캐시됨을 의미합니다.

무효화

Full Route Cache를 무효화하는 두 가지 방법이 있습니다:

  • 데이터 재검증: Data Cache를 재검증하면 서버에서 컴포넌트를 다시 렌더링하고 새 렌더 출력을 캐싱하여 Router Cache가 무효화됩니다.
  • 재배포: 배포 간에 지속되는 Data Cache와 달리, Full Route Cache는 새 배포에서 지워집니다.

옵트아웃

Full Route Cache를 옵트아웃하거나, 다시 말해 들어오는 모든 요청에 대해 컴포넌트를 동적으로 렌더링하려면 다음과 같이 할 수 있습니다:

  • Dynamic API 사용: 이렇게 하면 라우트가 Full Route Cache에서 옵트아웃되고 요청 시간에 동적으로 렌더링됩니다. Data Cache는 여전히 사용할 수 있습니다.
  • dynamic = 'force-dynamic' 또는 revalidate = 0 라우트 세그먼트 구성 옵션 사용: 이렇게 하면 Full Route Cache와 Data Cache를 건너뜁니다. 즉, 서버로 들어오는 모든 요청에 대해 컴포넌트가 렌더링되고 데이터가 가져와집니다. Router Cache는 클라이언트 측 캐시이므로 여전히 적용됩니다.
  • Data Cache 옵트아웃: 라우트에 캐시되지 않은 fetch 요청이 있으면, 이렇게 하면 라우트가 Full Route Cache에서 옵트아웃됩니다. 특정 fetch 요청에 대한 데이터는 들어오는 모든 요청에 대해 가져와집니다. 명시적으로 캐싱을 활성화하는 다른 fetch 요청은 여전히 Data Cache에서 캐시됩니다. 이렇게 하면 캐시된 데이터와 캐시되지 않은 데이터의 하이브리드가 가능합니다.

클라이언트 측 Router Cache

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

사용자가 라우트 간을 네비게이션할 때, Next.js는 방문한 라우트 세그먼트를 캐시하고 사용자가 네비게이션할 가능성이 있는 라우트를 프리페치합니다. 이는 즉시 뒤로/앞으로 네비게이션, 네비게이션 간 전체 페이지 리로드 없음, 공유 레이아웃에서 브라우저 상태와 React 상태 보존을 결과로 가져옵니다.

Router Cache와 함께:

  • 레이아웃은 네비게이션에서 캐시되고 재사용됩니다(부분 렌더링).
  • 로딩 상태는 즉시 네비게이션을 위해 네비게이션에서 캐시되고 재사용됩니다.
  • 페이지는 기본적으로 캐시되지 않지만 브라우저 뒤로 및 앞으로 네비게이션 중에는 재사용됩니다. 실험적인 staleTimes 구성 옵션을 사용하여 페이지 세그먼트에 대한 캐싱을 활성화할 수 있습니다.
알아두면 좋은 점
이 캐시는 특히 Next.js와 Server Components에 적용되며, 유사한 결과를 가지지만 브라우저의 bfcache와는 다릅니다.

지속 시간

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

  • 세션: 캐시는 네비게이션 간에 지속됩니다. 하지만 페이지 새로고침에서는 지워집니다.
  • 자동 무효화 기간: 레이아웃과 로딩 상태의 캐시는 특정 시간 후에 자동으로 무효화됩니다. 지속 시간은 리소스가 프리페치된 방법과 리소스가 정적으로 생성되었는지에 따라 달라집니다:
    • 기본 프리페치(prefetch={null} 또는 지정되지 않음): 동적 페이지는 캐시되지 않음, 정적 페이지는 5분
    • 전체 프리페치(prefetch={true} 또는 router.prefetch): 정적 및 동적 페이지 모두 5분

페이지 새로고침은 모든 캐시된 세그먼트를 지우지만, 자동 무효화 기간은 프리페치된 시점부터 개별 세그먼트에만 영향을 줍니다.

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

무효화

Router Cache를 무효화하는 두 가지 방법이 있습니다:

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

옵트아웃

Next.js 15부터 페이지 세그먼트는 기본적으로 옵트아웃됩니다.

알아두면 좋은 점
<Link> 컴포넌트의 prefetch prop을 false로 설정하여 프리페치도 옵트아웃할 수 있습니다.

캐시 상호작용

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

Data Cache와 Full Route Cache

  • Data Cache를 재검증하거나 옵트아웃하면 렌더 출력이 데이터에 의존하므로 Full Route Cache가 무효화됩니다.
  • Full Route Cache를 무효화하거나 옵트아웃해도 Data Cache에는 영향을 주지 않습니다. 캐시된 데이터와 캐시되지 않은 데이터를 모두 가진 라우트를 동적으로 렌더링할 수 있습니다. 이는 페이지의 대부분이 캐시된 데이터를 사용하지만 요청 시간에 가져와야 하는 데이터에 의존하는 몇 개의 컴포넌트가 있을 때 유용합니다. 모든 데이터를 다시 가져오는 성능 영향을 걱정하지 않고 동적으로 렌더링할 수 있습니다.

Data Cache와 클라이언트 측 Router cache

  • Data Cache와 Router cache를 즉시 무효화하려면 Server Action에서 revalidatePath 또는 revalidateTag를 사용할 수 있습니다.
  • Route Handler에서 Data Cache를 재검증해도 Route Handler가 특정 라우트에 연결되어 있지 않으므로 Router Cache가 즉시 무효화되지 않습니다. 이는 Router Cache가 하드 새로고침이나 자동 무효화 기간이 경과할 때까지 이전 페이로드를 계속 제공함을 의미합니다.

API

다음 표는 다양한 Next.js API가 캐싱에 어떤 영향을 미치는지에 대한 개요를 제공합니다:

API Router Cache Full Route Cache Data Cache React Cache
<Link prefetch> Cache
router.prefetch Cache
router.refresh Revalidate
fetch Cache Cache (GET and HEAD)
fetch options.cache Cache or Opt out
fetch options.next.revalidate Revalidate Revalidate
fetch options.next.tags Cache Cache
revalidateTag Revalidate (Server Action) Revalidate Revalidate
revalidatePath Revalidate (Server Action) Revalidate Revalidate
const revalidate Revalidate or Opt out Revalidate or Opt out
const dynamic Cache or Opt out Cache or Opt out
cookies Revalidate (Server Action) Opt out
headers, searchParams Opt out
generateStaticParams Cache
React.cache Cache
unstable_cache Cache

<Link>

기본적으로 <Link> 컴포넌트는 Full Route Cache에서 라우트를 자동으로 프리페치하고 React Server Component Payload를 Router Cache에 추가합니다.

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

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

router.prefetch

useRouter 훅의 prefetch 옵션을 사용하여 라우트를 수동으로 프리페치할 수 있습니다. 이는 React Server Component Payload를 Router Cache에 추가합니다.

useRouter 훅 API 참조를 확인하세요.

router.refresh

useRouter 훅의 refresh 옵션을 사용하여 라우트를 수동으로 새로고침할 수 있습니다. 이는 Router Cache를 완전히 지우고 현재 라우트에 대해 서버에 새 요청을 만듭니다. refresh는 Data나 Full Route Cache에 영향을 주지 않습니다.

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

useRouter 훅 API 참조를 확인하세요.

fetch

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

fetch의 기본 캐싱 동작(cache 옵션이 지정되지 않은 경우)은 cache 옵션을 no-store로 설정하는 것과 같습니다:

let data = await fetch('https://api.vercel.app/blog', { cache: 'no-store' })

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

fetch options.cache

cache 옵션을 force-cache로 설정하여 개별 fetch를 캐싱에 옵트인할 수 있습니다:

// 캐싱에 옵트인
fetch(`https://...`, { cache: 'force-cache' })

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

fetch options.next.revalidate

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

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

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

fetch options.next.tags와 revalidateTag

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

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

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

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

그런 다음 태그로 revalidateTag를 호출하여 캐시 항목을 제거합니다:

// 특정 태그로 항목 재검증
revalidateTag('a')

revalidateTag를 사용할 수 있는 두 곳이 있습니다. 달성하려는 목표에 따라 다릅니다:

  1. Route Handlers - 제3자 이벤트(예: 웹훅)에 대한 응답으로 데이터를 재검증합니다. Route Handler가 특정 라우트에 연결되어 있지 않으므로 Router Cache를 즉시 무효화하지 않습니다.
  2. Server Actions - 사용자 작업(예: 폼 제출) 후 데이터를 재검증합니다. 이는 연결된 라우트에 대한 Router Cache를 무효화합니다.

revalidatePath

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

revalidatePath('/')

revalidatePath를 사용할 수 있는 두 곳이 있습니다. 달성하려는 목표에 따라 다릅니다:

  1. Route Handlers - 제3자 이벤트(예: 웹훅)에 대한 응답으로 데이터를 재검증합니다.
  2. Server Actions - 사용자 상호작용(예: 폼 제출, 버튼 클릭) 후 데이터를 재검증합니다.

더 많은 정보는 revalidatePath API 참조를 확인하세요.

revalidatePath vs. router.refresh
router.refresh를 호출하면 Router cache를 지우고 Data Cache나 Full Route Cache를 무효화하지 않고 서버에서 라우트 세그먼트를 다시 렌더링합니다.

차이점은 revalidatePath는 Data Cache와 Full Route Cache를 제거하는 반면, router.refresh()는 클라이언트 측 API이므로 Data Cache와 Full Route Cache를 변경하지 않는다는 것입니다.

Dynamic APIs

cookiesheaders 같은 Dynamic APIs와 Pages의 searchParams prop은 런타임 수신 요청 정보에 의존합니다. 이들을 사용하면 라우트가 Full Route Cache에서 옵트아웃됩니다. 즉, 라우트가 동적으로 렌더링됩니다.

cookies

Server Action에서 cookies.set 또는 cookies.delete를 사용하면 쿠키를 사용하는 라우트가 stale해지는 것을 방지하기 위해 Router Cache가 무효화됩니다(예: 인증 변경을 반영하기 위해).

cookies API 참조를 확인하세요.

Segment Config Options

Route Segment Config 옵션은 라우트 세그먼트 기본값을 재정의하거나 fetch API를 사용할 수 없는 경우(예: 데이터베이스 클라이언트 또는 제3자 라이브러리)에 사용할 수 있습니다.

다음 Route Segment Config 옵션들은 Full Route Cache에서 옵트아웃됩니다:

  • const dynamic = 'force-dynamic'

이 구성 옵션은 모든 fetch를 Data Cache에서 옵트아웃시킵니다(즉, no-store):

  • const fetchCache = 'default-no-store'

더 고급 옵션을 보려면 fetchCache를 확인하세요.

더 많은 옵션은 Route Segment Config 문서를 확인하세요.

generateStaticParams

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

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

export async function generateStaticParams() {
  const posts = await fetch('https://.../posts').then((res) => res.json())

  return posts.map((post) => ({
    slug: post.slug,
  }))
}

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

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'을 활용하세요:

export async function generateStaticParams() {
  return []
}
알아두면 좋은 점
비어있더라도 generateStaticParams에서 배열을 반환해야 합니다. 그렇지 않으면 라우트가 동적으로 렌더링됩니다.
export const dynamic = 'force-static'

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

React cache 함수

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

GET 또는 HEAD 메서드를 사용하는 fetch 요청은 자동으로 메모이제이션되므로 React cache로 감쌀 필요가 없습니다. 하지만 다른 fetch 메서드나 요청을 고유하게 메모이제이션하지 않는 데이터 페칭 라이브러리(일부 데이터베이스, CMS, GraphQL 클라이언트 등)를 사용할 때는 cache를 사용하여 데이터 요청을 수동으로 메모이제이션할 수 있습니다.

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
})
💡 실무 팁
캐싱 전략 최적화를 위한 실무 지침:

  • 데이터 특성에 따른 캐싱 선택: 자주 변경되지 않는 데이터는 Data Cache를, 실시간성이 중요한 데이터는 Request Memoization만 활용하세요.
  • ISR(Incremental Static Regeneration) 활용: revalidate 옵션을 사용하여 정적 생성과 동적 업데이트의 장점을 모두 얻으세요.
  • 태그 기반 재검증 설계: 관련 데이터를 그룹화하여 효율적인 캐시 무효화 전략을 구성하세요.
  • 성능 모니터링: Next.js 개발자 도구나 Analytics를 통해 캐시 적중률을 모니터링하고 최적화하세요.

원문: https://nextjs.org/docs/app/guides/caching