핵심 요약
Next.js 15부터 fetch의 기본값이 no-store로 바뀌었다. 16에서는 'use cache' 디렉티브가 stable이 되어 캐시 전략을 코드 수준에서 명시할 수 있다.
- 기본: 캐시 안 함 (no-store)
- force-cache: 영구 캐시 (수동 revalidate 전까지)
- revalidate: N: N초마다 재생성
- 'use cache': 함수/컴포넌트 단위 캐시 선언
fetch 옵션의 4가지 상태
// 1. 기본: 매 요청 새로 가져옴
const a = await fetch(url);
// 2. 영구 캐시
const b = await fetch(url, { cache: 'force-cache' });
// 3. 60초 ISR
const c = await fetch(url, { next: { revalidate: 60 } });
// 4. 태그 기반 무효화
const d = await fetch(url, { next: { tags: ['posts'] } });
'use cache' 디렉티브
함수·파일·컴포넌트 최상단에 선언하면 해당 범위가 캐시된다. fetch 단위가 아닌 렌더 결과 단위 캐시라는 점이 차이.
// 함수 단위
async function getUser(id: string) {
'use cache';
return await db.user.findUnique({ where: { id } });
}
// 컴포넌트 단위
async function UserCard({ id }: { id: string }) {
'use cache';
const u = await getUser(id);
return <div>{u.name}</div>;
}
캐시 프로필 커스터마이징
// next.config.ts
export default {
cacheHandler: require.resolve('./cache-handler.js'),
experimental: {
cacheLife: {
blog: { stale: 60, revalidate: 300, expire: 3600 },
},
},
};
// 사용
'use cache';
cacheLife('blog');
무효화 전략
경로 기반
import { revalidatePath } from 'next/cache';
revalidatePath('/blog/[slug]', 'page');
태그 기반
import { revalidateTag } from 'next/cache';
revalidateTag('posts');
실전 의사결정 플로우
- 자주 바뀜 + 개인화: 기본(no-store). 그대로 두기.
- 공용 + 가끔 바뀜:
revalidate: 60~300 - 거의 고정 + 이벤트로 갱신: force-cache + revalidateTag
- 컴포넌트 단위 메모: 'use cache' + cacheLife
자주 묻는 질문
기본이 no-store인데 성능 걱정?
RSC는 서버 메모리에서 렌더하므로 fetch가 안 되더라도 빠르다. 정말 필요한 경우에만 캐시를 명시적으로 켜는 방향이 권장된다.
force-cache와 'use cache' 중복 사용?
'use cache'가 외곽이면 내부 fetch의 cache 옵션은 영향 없다. 상위 캐시 프로필이 적용된다.
dev 모드에서 캐시가 안 되는 이유?
개발 편의상 기본 비활성화. 검증은 next build && next start로 할 것.
댓글 0