Next.js 14~15의 App Router 문서는 캐시를 위치와 수명이 다른 네 계층으로 설명했다. Next.js 16에서 Cache Components를 켜면 캐싱을 'use cache'로 명시하는 모델로 바뀌고 문서도 이 이름들을 쓰지 않는다("use cache"와 Cache Components). 그래도 옛 코드와 아티클을 읽으려면 이 그림이 필요하다(2026 기준).
| 계층 | 위치 | 대상 | 수명 |
|---|---|---|---|
| Request Memoization | 서버, 렌더 중 메모리 | 같은 fetch 호출 결과 | 요청 한 번 |
| Data Cache | 서버 저장소 | fetch 결과 | revalidate 전까지, 요청·사용자 넘어 |
| Full Route Cache | 서버 | 라우트의 HTML(HyperText Markup Language) + RSC(React Server Components) 페이로드 | 재생성 전까지 |
| Router Cache | 브라우저 메모리 | 방문·prefetch한 라우트의 RSC 페이로드 | 세션 또는 정해진 시간(버전마다 다름) |
- Request Memoization: 한 렌더 안에서 같은 URL(Uniform Resource Locator)·옵션의
fetch를 한 번만 실행한다. 그래서generateMetadata와 페이지가 같은 데이터를 각자 불러도 된다. fetch가 아닌 함수는 React.cache로 감싸 같은 효과를 낸다. "캐시"라기보다 중복 제거다 - Data Cache:
fetch(url, { next: { revalidate: 60, tags: ['products'] } })처럼 요청을 넘어 결과를 저장한다. ISR(Incremental Static Regeneration)이 사실상 이것이다 - Full Route Cache: 정적으로 렌더된 라우트 결과를 통째로 저장한다. Data Cache가 무효화되면 다시 만들어진다
- Router Cache: 뒤로 가기와
<Link>prefetch를 서버 요청 없이 즉시 처리한다 - 무효화: 데이터를 바꾼 뒤
revalidateTag('products')나revalidatePath('/products')를 부른다. 보통 서버 액션 안에서 부른다. 16부터는revalidateTag(tag, 'max')처럼cacheLife프로필을 함께 넘겨야 한다
여러 인스턴스에서 캐시 공유하기
직접 운영(self-hosting)할 때 서버 캐시는 기본으로 프로세스 메모리(와 로컬 디스크)에 있다. 인스턴스가 여러 대면 각자 다른 캐시를 갖고, 재시작하면 사라진다. 그래서 같은 페이지가 인스턴스마다 다른 시점의 데이터를 보여 주고, 배포·재시작 직후에는 모든 인스턴스가 콜드 상태로 원본 API를 두드린다. 공유 저장소(Redis 등)를 쓰는 커스텀 캐시 핸들러로 이를 해결한다. 설정 이름이 두 개라 헷갈린다(Next.js 16.3 기준).
| 설정 | 대상 |
|---|---|
cacheHandler(단수) | ISR·라우트 핸들러 응답·최적화 이미지 등 기존 서버 캐시. 기본 메모리 캐시를 끄려면 cacheMaxMemorySize: 0 |
cacheHandlers(복수) | 'use cache'(default)와 'use cache: remote'(remote) 저장소. 이름을 더 붙여 'use cache: <이름>'으로도 쓴다 |
- 문서는 "대부분의 앱은 커스텀 핸들러가 필요 없다"고 먼저 말한다. 인스턴스가 하나이고 디스크가 유지되면 기본값으로 충분하다
'use cache'는 메모리에 두고, 비싸고 공유할 가치가 있는 것만'use cache: remote'로 원격 저장소에 보내는 식으로 나눌 수 있다. 원격은 네트워크 왕복과 인프라 비용이 붙는다- 태그 무효화도 인스턴스 사이에 퍼져야 한다.
cacheHandlers의refreshTags()를 구현해 요청마다 공유 저장소의 태그 상태를 동기화한다 'use cache'의 캐시 키에는 인자뿐 아니라 빌드 ID(deploymentId를 설정하면 그 값)와 함수 ID가 들어간다. 배포할 때마다 새 키 공간이 채워지고 이전 키는 축출될 때까지 남는다. 용량을 잡을 때 이 이중 적재를 고려한다(Redis (인메모리 저장소))
왜 바뀌었나
14까지는 fetch가 기본으로 캐시됐고, 15에서 fetch 기본값이 바뀐 뒤에도 라우트는 자동으로 정적 렌더돼 "왜 데이터가 안 바뀌지?"가 흔했다. 16은 기본을 "매 요청 실행"으로 두고 캐시할 곳만 표시하게 뒤집었다. 네 계층은 각각 메모이제이션(Memoization), 데이터 캐시, 페이지 캐시, 클라이언트 캐시라는 일반적인 층에 대응한다. 클라이언트 쪽 데이터 캐시는 TanStack Query, 브라우저 HTTP(HyperText Transfer Protocol) 캐시는 브라우저 캐시과 비교해 보면 좋다.
출처: Next.js - cacheHandlers · Next.js - cacheHandler · Next.js - use cache: Cache keys · Next.js - Self-Hosting: Shared cache