
Next.js App Router를 사용하다 보면 이런 상황을 자주 겪게 된다.
이런 문제의 대부분은
👉 Next.js의 캐싱 구조를 정확히 이해하지 못해서 발생한다.
Next.js는 단순한 캐싱이 아니라,
여러 레이어의 캐싱이 동시에 동작하는 구조를 가지고 있다.
Next.js의 캐싱은 크게 3가지 레이어로 나뉜다.
[Request]
↓
[Request Memoization]
↓
[Data Cache]
↓
[Full Route Cache]
👉 이 구조를 이해하면 대부분의 캐싱 문제를 설명할 수 있다.
하나의 요청 사이클 내에서
같은 fetch 요청을 여러 번 보내지 않도록 하는 메커니즘
await fetch('/api/data');
await fetch('/api/data'); // 실제로는 한 번만 요청됨
👉 "왜 fetch를 두 번 했는데 한 번만 호출되지?"의 원인
fetch 요청 결과를 캐싱하는 레이어
👉 Next.js에서는 기본적으로 fetch가 캐시된다
await fetch('/api/data'); // 기본적으로 캐싱됨
await fetch('/api/data', {
next: { revalidate: 60 },
});
await fetch('/api/data', {
cache: 'no-store',
});
👉 기본값이 "캐시됨"이라는 점이 중요하다
페이지 전체(HTML + 데이터)를 캐싱하는 레이어
👉 Static Rendering 시 자동 적용됨
export const dynamic = 'force-dynamic';
export const revalidate = 60;
👉 "페이지가 안 바뀌는 이유"는 대부분 이 레이어 때문
데이터가 변경되었을 때
👉 캐시를 갱신하는 방법
import { revalidatePath } from 'next/cache';
revalidatePath('/posts');
import { revalidateTag } from 'next/cache';
revalidateTag('posts');
👉 Server Action 이후
'use server';
export async function createPost() {
await db.post.create(...);
revalidatePath('/posts');
}
Next.js 캐시는 서버 중심이지만,
클라이언트에서도 별도의 캐시 전략이 존재한다.
| 구분 | 서버 캐시 | 클라이언트 캐시 |
|---|---|---|
| 위치 | 서버 | 브라우저 |
| 목적 | SSR 최적화 | UX / 인터랙션 |
| 도구 | fetch cache | React Query, SWR |
👉 두 캐시는 경쟁 관계가 아니라 보완 관계
캐싱 구조를 이해하는 것도 중요하지만,
실제로는 상황에 따라 어떤 전략을 선택할지가 더 중요하다.
Next.js에서는 데이터의 성격에 따라 캐싱 방식을 다르게 가져가는 것이 일반적이다.
사용자 정보, 주문 상태처럼
요청마다 데이터가 달라질 수 있는 경우에는 캐싱을 사용하지 않는 것이 적절하다.
await fetch('/api/data', {
cache: 'no-store',
});
또는
export const dynamic = 'force-dynamic';
이렇게 설정하면 요청마다 새로운 데이터를 기준으로 렌더링된다.
블로그 글, 정적 페이지처럼
데이터 변경이 거의 없는 경우에는 캐싱을 사용하는 것이 유리하다.
이 경우에는 별도의 설정 없이 기본 fetch를 사용해도
Next.js가 자동으로 캐싱을 적용한다.
게시글 목록, 상품 리스트처럼
완전히 실시간일 필요는 없지만 일정 주기로 갱신되어야 하는 데이터는
revalidate를 사용하는 방식이 적절하다.
await fetch('/api/data', {
next: { revalidate: 60 },
});
이 방식은 캐시된 데이터를 먼저 제공하고,
이후 백그라운드에서 최신 데이터를 반영한다.
데이터를 변경한 이후에는 기존 캐시가 유지되기 때문에
명시적으로 캐시를 갱신해줘야 한다.
revalidatePath('/posts');
또는
revalidateTag('posts');
이 과정을 통해 변경된 데이터가 화면에 반영된다.
언제 무엇을 쓸까
no-store)revalidate 사용 (ISR)revalidatePath / revalidateTag무엇이 어떻게 동작할까
Request Memoization: 동일 요청을 한 번만 실행하여 중복 fetch 방지Data Cache: fetch 결과를 캐싱하여 동일 데이터 요청 시 재사용Full Route Cache: 페이지 전체를 캐싱하여 빠른 응답 제공Revalidation: 변경된 데이터가 반영되도록 캐시를 갱신Next.js의 캐싱은 각 데이터의 특성과 업데이트 방식에 따라
적절한 전략을 선택하는 것이 더 중요하다.
특히 Next.js는 여러 레이어의 캐싱이 함께 동작하기 때문에,
문제가 발생했을 때 "어느 레이어에서 캐시되고 있는지"를 먼저 파악하는 것이 중요하다.