Next.js 캐싱 전략 (Cache & Revalidation)

Dam·2026년 3월 19일

[Next.js]

목록 보기
21/28
post-thumbnail

Next.js App Router를 사용하다 보면 이런 상황을 자주 겪게 된다.

  • "데이터를 바꿨는데 왜 화면이 그대로지?"
  • "왜 어떤 요청은 다시 안 나가지?"
  • "revalidate를 했는데 왜 안 바뀌지?"

이런 문제의 대부분은
👉 Next.js의 캐싱 구조를 정확히 이해하지 못해서 발생한다.

Next.js는 단순한 캐싱이 아니라,
여러 레이어의 캐싱이 동시에 동작하는 구조를 가지고 있다.


전체 캐싱 구조

Next.js의 캐싱은 크게 3가지 레이어로 나뉜다.

  1. Request Memoization (요청 단위)
  2. Data Cache (데이터 단위)
  3. Full Route Cache (페이지 단위)

동작 흐름

[Request]
   ↓
[Request Memoization]
   ↓
[Data Cache]
   ↓
[Full Route Cache]

👉 이 구조를 이해하면 대부분의 캐싱 문제를 설명할 수 있다.


1. Request Memoization

개념

하나의 요청 사이클 내에서
같은 fetch 요청을 여러 번 보내지 않도록 하는 메커니즘

특징

  • 서버 렌더링 중에만 적용
  • 동일한 요청은 한 번만 실행됨
  • 이후 요청은 결과 재사용

예시

await fetch('/api/data');
await fetch('/api/data'); // 실제로는 한 번만 요청됨

핵심 포인트

  • 성능 최적화를 위한 내부 동작
  • 개발자가 직접 제어하지 않음

👉 "왜 fetch를 두 번 했는데 한 번만 호출되지?"의 원인


2. Data Cache

개념

fetch 요청 결과를 캐싱하는 레이어

👉 Next.js에서는 기본적으로 fetch가 캐시된다


기본 동작

await fetch('/api/data'); // 기본적으로 캐싱됨

캐시 제어

1. revalidate (시간 기반)

await fetch('/api/data', {
  next: { revalidate: 60 },
});
  • 60초 동안 캐시 유지
  • 이후 요청 시 재검증

2. no-store (캐시 비활성화)

await fetch('/api/data', {
  cache: 'no-store',
});
  • 항상 최신 데이터 요청

핵심 포인트

  • Data Cache는 데이터 단위 캐싱
  • 대부분의 캐싱 이슈는 여기서 발생

👉 기본값이 "캐시됨"이라는 점이 중요하다


3. Full Route Cache

개념

페이지 전체(HTML + 데이터)를 캐싱하는 레이어

👉 Static Rendering 시 자동 적용됨


특징

  • 페이지 단위 캐싱
  • 빌드 시 또는 첫 요청 시 생성
  • 이후 동일 요청은 캐시된 결과 반환

라우트 옵션으로 제어

1. dynamic

export const dynamic = 'force-dynamic';
  • 항상 서버에서 렌더링
  • 캐싱 비활성화

2. revalidate

export const revalidate = 60;
  • 60초마다 페이지 재생성

핵심 포인트

  • Full Route Cache는 페이지 단위 캐싱
  • Data Cache와 함께 동작함

👉 "페이지가 안 바뀌는 이유"는 대부분 이 레이어 때문


4. Revalidation 전략

데이터가 변경되었을 때
👉 캐시를 갱신하는 방법


1. revalidatePath

import { revalidatePath } from 'next/cache';

revalidatePath('/posts');
  • 특정 페이지 캐시 무효화

2. revalidateTag

import { revalidateTag } from 'next/cache';

revalidateTag('posts');
  • 태그 기반 캐시 무효화

언제 사용하나?

👉 Server Action 이후

'use server';

export async function createPost() {
  await db.post.create(...);

  revalidatePath('/posts');
}

핵심 포인트

  • Mutation 이후 반드시 필요
  • 안 하면 데이터는 바뀌었지만 UI는 그대로

5. 클라이언트 캐시와의 관계

Next.js 캐시는 서버 중심이지만,
클라이언트에서도 별도의 캐시 전략이 존재한다.


예시

  • React Query
  • SWR

차이점

구분서버 캐시클라이언트 캐시
위치서버브라우저
목적SSR 최적화UX / 인터랙션
도구fetch cacheReact Query, SWR

👉 두 캐시는 경쟁 관계가 아니라 보완 관계


6. 언제 어떤 캐싱 전략을 써야 할까?

캐싱 구조를 이해하는 것도 중요하지만,
실제로는 상황에 따라 어떤 전략을 선택할지가 더 중요하다.

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 },
});

이 방식은 캐시된 데이터를 먼저 제공하고,
이후 백그라운드에서 최신 데이터를 반영한다.


데이터 변경(Mutation)이 발생하는 경우

데이터를 변경한 이후에는 기존 캐시가 유지되기 때문에
명시적으로 캐시를 갱신해줘야 한다.

revalidatePath('/posts');

또는

revalidateTag('posts');

이 과정을 통해 변경된 데이터가 화면에 반영된다.


캐싱 전략 선택

언제 무엇을 쓸까

  • 항상 최신 데이터 → 캐시 사용하지 않음 (no-store)
  • 거의 변하지 않는 데이터 → 캐싱 활용 (Static)
  • 주기적으로 변하는 데이터 → revalidate 사용 (ISR)
  • 데이터 변경 이후 → revalidatePath / revalidateTag

캐싱 구조 요약

무엇이 어떻게 동작할까

  • Request Memoization: 동일 요청을 한 번만 실행하여 중복 fetch 방지
  • Data Cache: fetch 결과를 캐싱하여 동일 데이터 요청 시 재사용
  • Full Route Cache: 페이지 전체를 캐싱하여 빠른 응답 제공
  • Revalidation: 변경된 데이터가 반영되도록 캐시를 갱신

마무리

Next.js의 캐싱은 각 데이터의 특성과 업데이트 방식에 따라
적절한 전략을 선택하는 것이 더 중요하다.

특히 Next.js는 여러 레이어의 캐싱이 함께 동작하기 때문에,
문제가 발생했을 때 "어느 레이어에서 캐시되고 있는지"를 먼저 파악하는 것이 중요하다.

profile
⏰ Good things take time

0개의 댓글