Next.js 16 Cache Components와 use cache 정리
Next.js 16에서는 App Router의 캐싱 모델을 더 명시적으로 다루기 위한 Cache Components가 들어왔다.
기존에는 route segment config, fetch 옵션, unstable_cache 같은 API를 조합해서 캐싱을 제어했다.
Cache Components를 켜면 데이터는 기본적으로 동적이고, 캐시하고 싶은 page, component, function에 use cache를 붙이는 방식으로 생각할 수 있다.
App Router 캐싱 모델의 변화
Cache Components의 핵심은 “기본은 동적, 필요한 부분만 명시적으로 캐시”라는 방향이다.cacheComponents를 켜면 Next.js는 정적 HTML shell을 먼저 만들고, 준비되는 동적 콘텐츠는 스트리밍으로 채울 수 있다.
이 구조 덕분에 한 route 안에서 정적인 영역과 동적인 영역을 섞어 다루기 쉬워진다.
이전 모델에서는 dynamic, revalidate, fetchCache 같은 route segment config가 캐싱 판단의 중심이었다.
Cache Components에서는 이런 설정을 use cache, cacheLife, cacheTag로 옮겨가는 흐름이 권장된다.
cacheComponents 활성화
Cache Components는 Next.js 16 기준 기능이다.
프로젝트에서 사용하려면 next.config.ts에 cacheComponents: true를 추가한다.
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfigNext.js 15에서 experimental.dynamicIO, experimental.useCache, experimental PPR을 사용했다면 Next.js 16의 cacheComponents로 마이그레이션하는 흐름을 확인해야 한다.cacheComponents는 PPR, useCache, dynamicIO를 하나의 설정으로 묶는 역할을 한다.
use cache의 역할
use cache는 function, component, file 단위로 캐싱 범위를 선언하는 directive다.
캐시하고 싶은 async function이나 component의 맨 위에 작성한다.
async function getPost(slug: string) {
'use cache'
const res = await fetch(`https://api.example.com/posts/${slug}`)
return res.json()
}이렇게 하면 함수의 입력과 내부에서 접근한 값을 기준으로 결과를 캐시할 수 있다.
Cache Components를 켠 상태에서는 fetch 안에 cache: 'force-cache'나 next: { revalidate }를 직접 붙이기보다, 캐시 범위를 use cache로 감싸고 수명과 태그를 별도 API로 표현하는 쪽이 더 명확하다.
cacheLife로 수명 지정
cacheLife는 use cache 범위 안에서 캐시 수명을 지정하는 함수다.
호출하지 않으면 기본 profile이 적용된다.
import { cacheLife } from 'next/cache'
async function getPosts() {
'use cache'
cacheLife('hours')
const res = await fetch('https://api.example.com/posts')
return res.json()
}cacheLife는 module scope에서 호출할 수 없다.
캐시가 선언된 function이나 component 안에서 직접 호출해야 한다.
공유 utility로 숨기기보다 캐시 동작이 필요한 위치에 드러내는 편이 읽기 좋다.
cacheTag와 revalidateTag
cacheTag는 캐시된 결과에 태그를 붙이는 함수다.
나중에 특정 태그만 무효화할 수 있어서 게시글, 상품 목록, 문서처럼 일부 데이터만 갱신해야 하는 경우에 유용하다.
import { cacheLife, cacheTag } from 'next/cache'
async function getPost(slug: string) {
'use cache'
cacheLife('hours')
cacheTag('posts', `post:${slug}`)
const res = await fetch(`https://api.example.com/posts/${slug}`)
return res.json()
}태그를 무효화할 때는 Server Function이나 Route Handler에서 revalidateTag를 호출한다.
import { revalidateTag } from 'next/cache'
export async function updatePost(slug: string) {
await fetch(`https://api.example.com/posts/${slug}`, {
method: 'PATCH',
})
revalidateTag(`post:${slug}`, 'max')
}Next.js 16 기준으로 revalidateTag는 profile을 함께 전달하는 방식이 권장된다.'max' profile은 stale-while-revalidate 방식으로 동작해서, 사용자는 오래된 데이터를 먼저 받고 새 데이터는 백그라운드에서 준비된다.
기존 fetch cache와의 차이
이전에는 개별 fetch에 캐싱 의도를 직접 넣는 코드가 흔했다.
await fetch('https://api.example.com/posts', {
cache: 'force-cache',
next: {
revalidate: 3600,
tags: ['posts'],
},
})Cache Components에서는 이 관심사를 함수 단위로 올려서 표현한다.
async function getPosts() {
'use cache'
cacheLife('hours')
cacheTag('posts')
const res = await fetch('https://api.example.com/posts')
return res.json()
}차이는 작아 보이지만 읽는 위치가 달라진다.fetch 한 줄의 옵션을 해석하기보다, 함수 전체가 어떤 수명과 태그를 가지는 캐시인지 먼저 보게 된다.
적용 전 확인할 점
Cache Components를 켜면 uncached dynamic data가 개발 중 오류로 드러날 수 있다.
이 오류는 캐시해야 하는 데이터에는 use cache를 붙이고, 요청 시점 데이터가 필요한 영역은 Suspense로 감싸라는 방향을 알려준다.
또 하나는 저장 위치다.
공식 문서 기준 use cache의 기본 저장은 in-memory이고, serverless instance가 사라지면 캐시도 사라질 수 있다.
인스턴스 종료 후에도 유지되는 캐시가 필요하다면 use cache: remote나 cache handler 구성을 검토해야 한다.
새 배포 이후에는 durable storage를 쓰더라도 캐시 값이 다시 계산될 수 있다는 점도 염두에 둔다.
정리
Next.js 16의 Cache Components는 캐싱을 더 명시적인 단위로 옮긴다.
route segment config나 fetch 옵션에 흩어져 있던 캐시 정책을 use cache, cacheLife, cacheTag 중심으로 정리하는 흐름이다.
처음에는 설정이 하나 더 늘어난 것처럼 보이지만, 캐시 범위와 수명이 코드에서 바로 보인다는 점이 장점이다.
참고 자료




