본문 바로가기
Frontend2026년 4월 11일4분 읽기

Next.js 16 App Router 캐시 완벽 정리 — force-cache, revalidate, use cache 실전 차이

YS
김영삼
조회 641

핵심 요약

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

아직 댓글이 없습니다.
Ctrl+Enter로 등록