웹 애플리케이션에서 캐싱(Cache) 이란, 어떤 데이터나 결과를 임시로 저장해 두는 것을 말한다.
예를 들어, 사용자가 "상품 목록 보기" 페이지를 열었을 때, 서버는 상품 데이터를 불러와서 화면에 보여준다. 그런데 같은 사용자가 몇 초 후 다시 같은 페이지로 이동한다면, 다시 데이터를 불러올 필요가 있을까?
❌ 불필요한 데이터 요청 = 서버 낭비 + 로딩 느림
✅ 이미 불러온 데이터를 재사용 = 빠른 반응 + 효율적인 처리
이처럼, 이미 가져온 데이터나 렌더링 결과를 저장해두고, 나중에 같은 요청이 오면 다시 사용하는 기술이 바로 캐싱이다.
Next.js는 브라우저와 서버가 협력해서 동작하는 프레임워크이다.
따라서 캐싱도 "서버"와 "클라이언트"라는 두 환경에서 각기 다르게 동작한다.
참고: 대부분의 캐싱은 서버 환경에서 작동하고, 클라이언트 쪽에는 보조적인 캐시만 존재한다.
서버에서 실행되는 컴포넌트(Server Component)나 API(Route Handler)에서는 데이터 요청, 렌더링 결과, HTML 등을 저장하고 재사용하는 고급 캐싱 기능이 적용된다.
대표적으로 아래 세 가지가 있다.
Request Memoization: 서버 렌더링 중 중복된 fetch 요청을 한 번만 실행Data Cache: 외부 API나 DB 데이터를 서버에 저장하고 여러 요청 간 재사용Full Route Cache: HTML과 RSC Payload 전체를 저장해서 페이지 전체를 캐싱이 세 가지는 모두 서버 컴포넌트(Server Component) 또는 서버 API(Route Handler)에서만 작동한다.
즉, "use client"가 선언된 클라이언트 컴포넌트에서는 적용되지 않는다.
참고: RSC = React Server Component
브라우저 환경에서도 캐시가 존재한다. 하지만 서버처럼 데이터를 따로 저장하거나 fetch를 재사용하진 않는다. 클라이언트에서 사용하는 캐시는 오직 Router Cache 하나뿐이다.
즉, 클라이언트 캐시는 서버 결과물을 전체 덩어리로 저장해두는 방식이다. 서버처럼 fetch() 결과를 따로 캐싱하거나 API 결과를 저장하지는 않는다.
📌 참고:
Full Route Cache와Router Cache는 모두 페이지 전체를 저장하지만,
Full Route Cache는 서버가 다시 렌더링하지 않기 위해,
Router Cache는 클라이언트가 서버에 다시 요청하지 않기 위해 존재한다.
Next.js는 서버에서 HTML을 생성하거나, 데이터를 가져와야 하는 구조이기 때문에, 다음과 같은 이슈가 발생할 수 있다.
그래서 Next.js는 내부적으로 다음과 같은 네 가지 캐싱 시스템을 제공한다. 이 네 가지는 각각의 역할과 캐싱 위치, 목적이 다르다.
| 메커니즘 이름 | 무엇을 캐싱하는가? | 어디에 저장되는가? (작동 환경) | 공유 범위 | 목적 | 지속 시간 |
|---|---|---|---|---|---|
| Request Memoization | 서버 컴포넌트 안의 fetch() 결과 | 서버 (요청 중 메모리) | 요청 1건 내에서만 | 렌더링 중 같은 fetch가 여러 번 호출돼도 요청은 한 번만 발생 | 하나의 요청 처리 중에만 유지 (짧음) |
| Data Cache | 외부 API나 DB에서 가져온 데이터 | 서버 | 모든 사용자와 공유 | 같은 fetch 요청이 다시 들어올 때, 서버에 저장된 응답을 재사용 (렌더링 이후에도 유효) | 서버에 지속 보관됨 (시간 설정 또는 수동으로 갱신 가능) |
| Full Route Cache | 페이지 렌더링 결과 (HTML + RSC Payload) | 서버 | 모든 사용자와 공유 | 동일한 경로 요청 시 서버가 다시 렌더링하지 않도록 함 | 서버에 지속 보관됨 (시간 설정 또는 수동으로 갱신 가능) |
| Router Cache | 서버에서 받은 RSC Payload 전체 | 클라이언트 (브라우저 메모리) | 본인만 사용 가능 | 페이지 전환 시 서버에 다시 요청하지 않고 즉시 렌더링 | 탭을 닫을 때까지 or 일정 시간 후 자동 삭제 |
Next.js는 대부분의 캐싱을 자동으로 처리해준다.
하지만, 모든 상황에 완벽하게 맞출 수는 없기 때문에, 개발자가 아래와 같은 것들을 직접 설정할 수도 있다.
이런 설정들을 위해 다양한 API와 옵션들(fetch 옵션, revalidateTag, router.refresh 등)이 제공된다.

[유저 요청]
↓
[서버]
├── Request Memoization (요청 중 중복 제거)
├── Data Cache (데이터 저장소)
└── Full Route Cache (페이지 결과 저장소)
↓
[클라이언트]
└── Router Cache (브라우저 메모리)
이렇게 4단계로 구성된 캐시 시스템은, 사용자가 같은 페이지를 다시 방문할 때 서버나 데이터베이스에 요청하지 않아도 빠르게 응답할 수 있도록 돕는다.

Request Memoization(요청 메모이제이션)은 같은 fetch 요청이 여러 번 발생하더라도, 실제로는 한 번만 실행하고 결과를 재사용하는 기능이다.
예를 들어 어떤 React 컴포넌트에서 fetch("https://api.example.com/item/1")를 호출했다고 할 때, 다른 컴포넌트에서도 같은 URL로 fetch를 호출하면, Next.js는 네트워크 요청을 다시 보내지 않고, 처음 가져온 데이터를 그대로 다시 사용한다.
이것이 바로 “메모이제이션(Memoization)”이라는 기술이다.
React 기반의 서버 컴포넌트 구조에서는 다음과 같은 데이터 요청의 비효율성 문제가 자주 발생한다.
예를 들어, 다음과 같은 경우를 생각할 수 있다.
Layout 컴포넌트에서 사용자 정보를 요청한다.Page 컴포넌트에서도 동일한 사용자 정보를 다시 요청한다.ProfileCard와 같은 하위 컴포넌트에서도 동일한 정보를 또 요청한다.이처럼 하나의 페이지에서 동일한 데이터 요청이 여러 번 발생하게 되며, 각 요청은 모두 같은 URL을 대상으로 한다. 결과적으로 서버는 같은 데이터에 대해 여러 번 네트워크 요청을 수행하게 되고, 이는 리소스 낭비로 이어진다.
Next.js는 React의 요청 메모이제이션 기능을 활용하여, 서버 컴포넌트 환경에서 동일한 fetch 요청에 대해 자동으로 결과를 재사용한다.
이를 통해 불필요한 중복 요청을 방지하고, 전체 페이지 렌더링의 효율성을 높일 수 있다.
Request Memoization은 Next.js 고유 기능은 아니며, React의 렌더링 최적화 기능에 기반한 동작이다. 다만, Next.js의 Data Cache, Full Route Cache, Router Cache 등 캐싱 시스템과 어떻게 함께 작동하는지를 설명하기 위해 공식 문서에서 언급되었다.

하나의 HTTP 요청이 서버에 도착해서 응답을 마칠 때까지의 전체 과정이다. 메모이제이션은 이 라이프사이클 동안만 유지된다.
1. 컴포넌트 A에서 fetch 실행
→ 아직 저장된 데이터가 없음 → 실제 요청 → 결과 저장 → cache MISS
2. 컴포넌트 B에서 같은 fetch 실행
→ 저장된 결과가 있음 → 네트워크 요청 없이 결과 반환 → cache HIT
3. 컴포넌트 C에서도 같은 fetch 실행
→ 또 cache HIT
4. 페이지 렌더링 완료
→ 모든 캐시 초기화 (요청 사이클 종료)
Request Memoization(요청 메모이제이션)은 모든 fetch 요청에 자동으로 적용되는 것은 아니다.
GET 요청에만 적용된다. 이는 GET 요청이 데이터를 읽기만 하고 상태를 변경하지 않는다는 특성 때문이다.POST, PUT, DELETE 등 변경을 유발하는 요청에는 적용되지 않는다. 이들은 요청마다 결과가 달라질 수 있기 때문에 캐시가 허용되지 않는다.Page, Layout, Template, generateMetadata, generateStaticParams → React 컴포넌트 트리 내부에서 동작하므로 메모이제이션이 적용된다.Route Handler (app/api/*), Middleware → React 트리 외부에서 실행되므로 메모이제이션이 적용되지 않는다.Request Memoization(요청 메모이제이션)은 요청 처리 중(렌더링 중)에만 적용되며, 요청이 완료되면 캐시도 초기화된다. 즉, 클라이언트 요청 하나에 대한 처리 사이클 동안만 유효하다.
다음에 같은 사용자가 다시 요청하면 처음부터 다시 캐시를 채운다.
💡 옵트아웃(Opt-out)
기본으로 활성화되어 있는 기능을 사용자가 명시적으로 끌 수 있는 선택권을 의미한다.예를 들어, 어떤 기능이 기본값으로 켜져 있지만, 옵션을 변경하거나 설정을 조정해서 끌 수 있다면 옵트아웃 가능이다.
Request Memoization(요청 메모이제이션)은 명시적으로 비활성화할 수 없다.
이 동작은 React 내부에서 자동으로 수행되는 최적화이므로, 개발자가 명시적으로 끌 수 있는 설정은 존재하지 않는다. 따라서 요청 메모이제이션은 옵트아웃할 수 없다.
특정 요청을 수동으로 중단(Abort)할 수는 있지만, 이는 메모이제이션을 끄는 것이 아니라 요청 실행 도중에 취소하는 것이다.
const controller = new AbortController();
const signal = controller.signal;
fetch('https://api.example.com/data', { signal });
// 어떤 조건일 때 요청을 중단한다
controller.abort();
AbortController는 네트워크 요청을 중단하는 기능이지, 메모이제이션을 끄는 방법은 아니다.
GET 요청과 React 컴포넌트 트리 내에서만 작동하며, 렌더링 완료 후 캐시가 초기화된다.Data Cache(데이터 캐시)는 서버에서 fetch()로 가져온 데이터를 요청 처리 중뿐만 아니라, 그 이후에도 서버에 저장해 두고 재사용할 수 있게 하는 기능이다.
즉, 한 번 데이터를 가져오면 서버가 그 결과를 기억해두고, 같은 요청이 다시 들어왔을 때는 외부 API나 DB를 다시 조회하지 않고 캐시된 결과를 그대로 반환한다.
Request Memoization은 단일 요청 내에서만 데이터를 기억하는 반면, Data Cache는 한 번 저장되면, 이후 다른 사용자의 요청이나 다음 페이지 요청에서도 그대로 재사용된다. 심지어 앱이 다시 배포되기 전까지는 그대로 유지된다.
웹 서비스에서는 동일한 데이터에 대한 요청이 빈번히 발생한다. 예를 들어, 블로그 게시글 목록이나 상품 카테고리 같은 데이터는 짧은 시간 동안 변경되지 않는 경우가 많다. 이러한 데이터를 매번 외부 서버에서 가져오면 다음과 같은 문제가 발생한다.
Data Cache는 데이터를 서버에 저장하여 동일한 요청에 대해 저장된 데이터를 빠르게 반환한다. 이를 통해 성능을 개선하고 비용을 절감한다.
Data Cache는 fetch() 요청을 통해 데이터를 저장하고 재사용하는 과정을 거친다.

1. 데이터 요청 발생
→ Data Cache 확인 → 데이터 있으면 반환 → cache HIT
2. 데이터 없으면
→ 외부 서버에서 가져옴 → 캐시에 저장 → 반환 → cache MISS
3. cache: 'no-store' 설정 시
→ 캐시 사용 안 함 → 외부 서버에서 직접 가져옴
참고: Data Cache는 fetch 요청의 URL과 옵션을 기준으로 데이터를 식별한다. 동일한 요청은 동일한 캐시 항목을 공유한다.
Next.js는 fetch()에 옵션을 추가하여 Data Cache의 동작을 제어한다. 주요 옵션은 cache와 next.revalidate이다.
참고:
cache옵션을 설정하지 않으면 Next.js는 기본적으로no-store처럼 동작한다. 캐시를 사용하려면force-cache를 명시적으로 지정해야 한다.
cache 옵션| 옵션 | 설명 |
|---|---|
force-cache | 캐시가 있으면 사용하고, 없으면 데이터를 가져와 캐시에 저장한다. |
no-store | 캐시를 사용하지 않고 매번 외부 서버에서 데이터를 가져온다. 캐시에 저장하지 않는다. |
// 캐시된 데이터 요청
fetch('https://api.example.com/news', {
cache: 'force-cache'
});
// 캐시를 사용하지 않는 요청
fetch('https://api.example.com/user', {
cache: 'no-store'
});
next.revalidate 옵션fetch('https://api.example.com/news', {
next: { revalidate: 60 } // 60초마다 캐시 갱신
});
revalidate는 캐시된 데이터의 유효 기간(초 단위)을 지정한다.지정된 시간이 지나면 캐시를 갱신한다.

1. 지정된 시간 동안
→ 캐시된 데이터 반환
2. 시간 초과 시
→ 기존 데이터 반환 → 백그라운드에서 새 데이터 요청
3. 새 데이터 수신
→ 성공 시 캐시 업데이트 → 실패 시 기존 데이터 유지
fetch('https://api.example.com/data', {
next: { revalidate: 300 } // 5분마다 갱신
});
revalidatePath('/news'): 지정된 경로의 캐시를 갱신한다.revalidateTag('news'): 지정된 태그로 묶인 캐시를 갱신한다.
1. 갱신 요청 발생
→ 해당 캐시 삭제
2. 다음 데이터 요청
→ 새 데이터 가져옴 → 캐시에 저장
// 데이터 요청 시 태그 지정
fetch('https://api.example.com/news', {
next: { tags: ['news'] }
});
// 태그로 캐시 갱신
revalidateTag('news');
| 위치 | 사용 가능 여부 |
|---|---|
| Page, Layout, Server Component | ✅ 가능 |
Route Handler (app/api/*) | ✅ 가능 |
| Middleware | ❌ 불가능 |
참고: Middleware에서는 fetch 요청이 항상
no-store처럼 동작하여 캐시를 지원하지 않는다.
Full Route Cache는 정적 경로의 페이지 렌더링 결과(HTML과 React Server Component Payload)를 서버에 저장하여 이후 요청에서 재사용하는 기능이다. Full Route Cache는 정적 경로에서만 기본적으로 작동하며, 동적 경로는 캐시되지 않는다.
💡 정적 페이지?
내용이 자주 변하지 않고 모든 사용자에게 동일하게 제공되는 페이지다.
(예: 회사 소개 페이지, 블로그 목록)
Next.js는 이런 페이지를 빌드할 때 미리 만들거나, 첫 요청 시 렌더링한 뒤 저장한다. 이후 같은 페이지 요청이 오면 새로 만들지 않고 저장된 결과를 바로 보낸다.
💡 React Server Component Payload란?
페이지의 구성 요소(컴포넌트)를 React가 서버에서 처리한 결과물로, 압축된 데이터 형식이다. 클라이언트는 이 데이터를 받아 화면을 완성한다. 쉽게 말해, 서버가 미리 그린 "설계도" 같은 것이다.
Data Cache와의 차이점
Data Cache는 fetch로 가져온 데이터만 저장하지만, Full Route Cache는 페이지 전체(HTML과 Payload)를 저장한다.
정적 페이지는 많은 사용자가 자주 방문하지만 내용이 거의 바뀌지 않는다. 매번 새로 렌더링하면 다음과 같은 문제가 생긴다.
Full Route Cache는 페이지를 한 번 만들어 저장해두고, 요청이 올 때마다 빠르게 보여줌으로써 성능을 높이고 비용을 줄인다. 예를 들어, 블로그 목록 페이지를 매번 새로 만들지 않고 저장된 버전을 보여주면 서버 부담이 훨씬 적다.

Full Route Cache는 정적 경로에서 페이지를 미리 만들어 저장하고 재사용한다. 동적 경로는 캐시되지 않는다. 아래는 그 과정이다.
1. 정적 경로의 경우
→ 빌드 시 렌더링 → HTML과 RSC Payload 캐시에 저장
2. 동일 정적 경로 요청
→ 캐시된 HTML과 RSC Payload 반환 → 렌더링 생략
3. 클라이언트 처리
→ HTML로 빠른 화면 표시 → RSC Payload로 상호작용 추가
4. 동적 경로의 경우
→ 요청 시 렌더링 → 캐시 사용 안 함

💡 Hydration?
Hydration은 서버에서 보낸 페이지를 클라이언트에서 "살아 움직이게" 만드는 과정이다. 예를 들어, 서버가 정적인 사진 같은 페이지를 주면, 클라이언트가 버튼을 누를 수 있게 동작을 추가하는 식이다.
Full Route Cache는 정적 경로에서 기본적으로 적용된다. 동적 경로로 바꾸거나 캐시 주기를 설정할 수 있다.
// app/about/page.tsx
export default async function AboutPage() {
return <h1>About Us</h1>;
}
동적 경로로 만들면 Full Route Cache가 적용되지 않는다:
| 옵션 | 설명 |
|---|---|
dynamic = 'force-dynamic' | 정적 경로를 동적으로 변경, 캐시 없이 요청 시 렌더링한다. |
revalidate = 0 | 정적 경로를 동적으로 변경, 캐시 없이 요청 시 렌더링한다. |
cookies(), headers() | 이런 함수를 사용하면 자동으로 동적 경로가 되어 캐시되지 않는다. |
fetch에 cache: 'no-store' | 데이터가 캐시되지 않으면 페이지도 동적으로 간주된다. |
// app/dashboard/page.tsx
export const dynamic = 'force-dynamic';
export default async function Dashboard() {
const res = await fetch('https://api.example.com/user', { cache: 'no-store' });
const user = await res.json();
return <p>Welcome, {user.name}!</p>;
}
정적 경로에 갱신 주기를 추가할 수 있다.
// app/products/page.tsx
export const revalidate = 3600; // 1시간마다 갱신
페이지 일부는 캐시하고, 일부는 동적으로 처리할 수 있다.
// app/products/page.tsx
export default async function ProductsPage() {
const staticData = await fetch('https://api.example.com/products', {
cache: 'force-cache',
}).then((res) => res.json());
const userData = await fetch('https://api.example.com/user', {
cache: 'no-store',
}).then((res) => res.json());
return (
<div>
<p>User: {userData.name}</p>
<ul>
{staticData.map((item: any) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
Full Route Cache는 정적 경로에서 기본적으로 오래 유지되지만, 갱신 방법이 있다.
지정된 시간이 지나면 캐시를 갱신한다.
// app/products/page.tsx
export const revalidate = 3600; // 1시간마다 갱신
1. 지정된 시간 동안
→ 캐시된 페이지 반환
2. 시간 초과 시
→ 기존 페이지 반환 → 백그라운드에서 새로 렌더링
3. 렌더링 완료
→ 성공 시 캐시 업데이트 → 실패 시 기존 캐시 유지
특정 이벤트로 캐시를 즉시 갱신한다.
revalidatePath('/products'): 특정 경로 갱신.revalidateTag('products'): 태그로 묶인 캐시 갱신.// app/api/revalidate/route.tsx
export async function POST() {
revalidateTag('products');
return Response.json({ message: 'Cache revalidated' });
}
1. 갱신 요청 발생
→ 해당 캐시 삭제
2. 다음 페이지 요청
→ 새로 렌더링 → 캐시에 저장
새 배포가 발생하면 모든 Full Route Cache가 삭제되고 새로 생성된다.
Data Cache가 갱신되면(예:
revalidateTag), 그 데이터를 사용하는 Full Route Cache도 갱신된다.
Full Route Cache는 페이지와 레이아웃에서만 동작한다.
| 위치 | 사용 가능 여부 |
|---|---|
| Page, Layout | ✅ 가능 |
Route Handler (app/api/*) | ❌ 불가능 |
| Middleware | ❌ 불가능 |
동적 경로로 설정된 페이지는 캐시되지 않는다.
dynamic = 'force-dynamic' 또는 revalidate = 0으로 정적 경로의 캐시를 끌 수 있다.Client-side Router Cache는 브라우저 메모리에 저장되는 Next.js의 전용 캐시 시스템이다.
Next.js에서는 페이지를 탐색할 때마다, 서버로부터 받은 RSC Payload를 클라이언트 측에서 메모리에 저장해두고, 이후 동일한 경로로 이동할 때 서버에 다시 요청하지 않고 재사용한다.
이렇게 하면 사용자는 페이지 전환 시마다 서버에 새 요청을 보내지 않고, 즉시 페이지를 렌더링할 수 있다. 브라우저 히스토리와 상태도 보존되므로 UX가 좋아진다.
Full Route Cache는 서버에서 렌더링된 HTML과 RSC Payload를 저장하여, 서버가 동일한 페이지 요청에 대해 다시 렌더링할 필요 없이 빠르게 응답할 수 있도록 해준다. 하지만 이 캐시는 어디까지나 서버를 위한 최적화 수단일 뿐이며, 클라이언트는 페이지를 이동할 때마다 여전히 서버에 새로운 요청을 보낸다.
즉, 서버가 렌더링을 재사용하는 것은 가능하지만, 클라이언트가 서버에 요청을 보내는 시도 자체를 막을 수는 없다. 이 지점에서 클라이언트 라우터 캐시가 필요해진다. 클라이언트 측에서 직접 RSC Payload를 기억하고 있다면, 아예 서버에 요청을 보내지 않고도 이전에 본 페이지를 즉시 렌더링할 수 있게 되기 때문이다.
따라서 Next.js는 서버의 응답 속도를 위한 Full Route Cache, 그리고 클라이언트의 인터랙션 속도와 상태 유지를 위한 Client-side Router Cache를 서로 보완적인 역할로 함께 제공한다. 두 시스템은 같은 페이지 결과물을 저장한다는 공통점이 있지만, 저장 위치, 사용 시점, 캐시 대상, 목적이 완전히 다르다.
Client-side Router Cache는 아래와 같이 동작한다.
1. 경로 방문
→ RSC 페이로드 캐싱
2. 재방문
→ 캐시 사용, 서버 요청 생략
3. 프리페치
→ 이동 가능 경로 캐싱
4. 새로고침
→ 캐시 삭제
Client-side Router Cache는 사용자가 사이트를 탐색하면서 각 경로 세그먼트(Layout, Loading, Page 등)의 RSC Payload를 클라이언트 메모리에 저장하는 방식으로 동작한다. 이 캐시는 일시적이기 때문에, 페이지를 새로 고침하거나 브라우저 탭을 닫으면 모두 삭제된다.
Layout 세그먼트)과 로딩 상태(Loading 세그먼트)는 자동으로 캐시되고, 이후 페이지 탐색 시 재사용되어 빠른 전환을 가능하게 한다.Page 세그먼트)는 기본적으로 캐시되지 않지만, 브라우저 뒤로/앞으로 탐색 시에는 캐시된 페이지가 재사용된다. staleTimes 옵션을 사용하면 페이지도 캐시 대상으로 지정할 수 있다.하지만 단순 탐색 중에는 캐시가 일정 시간 동안 유지되며, 이 유지 시간은 "해당 세그먼트가 prefetch(사전 로딩) 되었는가, 그리고 어떻게 되었는가"에 따라 달라진다. 즉, 프리페치(prefetch) 설정 방식에 따라 세그먼트별 자동 캐시 만료 시간이 달라진다.
prefetch={null} 또는 생략: 정적 세그먼트는 5분 유지, 동적 세그먼트는 캐시되지 않음 (0초)prefetch={true} 또는 router.prefetch(): 정적·동적 세그먼트 모두 5분간 유지프리페치로 캐시된 각 세그먼트는 프리패치된 시점부터 개별적으로 만료되며, 전체 페이지 단위가 아닌 세그먼트 단위로 관리된다. 즉, 하나의 페이지가 여러 세그먼트로 구성되어 있다면, 어떤 세그먼트는 캐시가 유지되고, 다른 세그먼트는 만료될 수도 있다.
프리페치(prefetch)는 사용자가 아직 클릭하지 않았지만, 앞으로 클릭할 가능성이 있는 경로의 데이터를 미리 불러오는 기능이다.
Next.js에서는
<Link>컴포넌트가 자동으로 이 작업을 수행한다. 사용자가 스크롤로<Link>태그를 화면에 보이게 하거나, 마우스를 올리면 Next.js가 백그라운드에서 해당 라우트의 데이터를 미리 요청한다.
Router Cache는 자동으로 만료되기도 하지만, 특정 동작에 의해 즉시 무효화(초기화) 될 수도 있다.
주로 서버 액션 또는 클라이언트 동작을 통해 발생하며, 다음과 같은 경우 캐시가 강제로 비워지고, 다음 요청 시 서버에 새로 요청하게 된다.
router.refresh() 호출 시클라이언트 컴포넌트에서 useRouter()를 통해 router.refresh()를 호출하면,
현재 라우트의 클라이언트 캐시가 무효화된다.
'use client'
import { useRouter } from 'next/navigation'
export default function RefreshButton() {
const router = useRouter()
return <button onClick={() => router.refresh()}>새로고침</button>
}
이 경우, 브라우저는 서버에 새 요청을 보내고, 최신 RSC Payload를 받아 다시 렌더링한다.
단, 이 호출은 Data Cache나 Full Route Cache에는 영향을 주지 않는다. 즉, 클라이언트 캐시만 무효화된다.
cookies.set() 또는 cookies.delete() 사용 시Server Action에서 인증 관련 쿠키를 설정하거나 삭제하면, 해당 라우트의 Router Cache가 무효화된다.
이는 인증 상태에 따라 페이지가 달라져야 하는 경우, 오래된 클라이언트 상태를 계속 보여주지 않기 위한 안전장치다.
revalidatePath() 또는 revalidateTag() 호출 시서버 액션에서 특정 경로(path)나 태그(tag)를 기준으로 데이터를 다시 가져오도록 요청하면, 해당 라우트의 Data Cache뿐 아니라 Router Cache까지도 무효화된다. 즉, 데이터가 바뀌면 화면도 반드시 최신 상태로 바뀌도록 보장해주는 흐름이다.
📌 단, 이 캐시 무효화는 Server Action 안에서 호출될 때 Router Cache까지 영향을 준다.
Route Handler에서 호출한 경우에는 Router Cache에는 영향을 주지 않는다.
router.refresh(), cookies.set(), revalidatePath() 등의 호출은 이 Router Cache를 즉시 무효화할 수 있다.Next.js는 다양한 캐시 메커니즘을 제공하는데, 이들 사이에는 상호작용(interactions)이 존재한다. 즉, 하나의 캐시를 무효화하거나 갱신하는 행위가, 다른 캐시에 연쇄적으로 영향을 줄 수도 있고, 전혀 영향을 주지 않을 수도 있다.
아래는 각 캐시 간의 구체적인 상호작용 관계를 설명한 내용이다.
fetch()로 가져온 데이터가 페이지 렌더링에 사용되기 때문에, 해당 데이터가 바뀌면 페이지 전체를 다시 렌더링해야 한다. 따라서 Data Cache가 무효화되면, 이 데이터를 사용한 페이지의 Full Route Cache도 함께 무효화된다.
반대로, Full Route Cache만 무효화하고 새로 렌더링하더라도, 내부에서 사용하는 fetch() 요청의 결과가 여전히 Data Cache에 저장되어 있다면 그 데이터를 그대로 다시 사용한다. 즉, 렌더링은 다시 하지만, fetch는 재사용됨.
이렇게 하면 한 페이지 안에서 일부 fetch만 캐시를 끄고, 나머지는 캐시를 그대로 사용할 수 있는 하이브리드 캐싱 구조를 만들 수 있다.
revalidatePath, revalidateTag 호출 시 → Router Cache도 무효화됨Server Action에서 revalidatePath() 또는 revalidateTag()를 호출하면, 해당 경로의 Router Cache까지 무효화된다.
'use server'
import { revalidatePath } from 'next/cache'
export async function submitForm() {
await updateDB()
revalidatePath('/posts')
}
위 코드 실행 시
/posts 관련 Data Cache/posts의 Full Route Cache/posts의 Router Cache (클라이언트 메모리 캐시)모두 무효화되어 다음 방문 시 서버에서 새로 데이터와 페이지를 받아온다.
만약 동일한 revalidatePath() 또는 revalidateTag()를 Route Handler(app/api/*) 내부에서 호출하면, Data Cache와 Full Route Cache는 무효화되지만 Router Cache는 무효화되지 않는다.
왜냐하면 Route Handler는 특정 라우트와 1:1로 연결되어 있지 않기 때문이다.
결과적으로 클라이언트는 이전 페이지의 캐시된 상태를 유지하고, 자동 갱신되지 않으며, 사용자가 새로고침하거나 자동 무효화 시간이 지나야 서버에 재요청이 발생한다.
이 둘은 구조상 서로 다른 위치(서버 vs 브라우저)에 존재하지만, 간접적으로 연결되어 있다.
router.refresh()로 수동 갱신할 수 있다.결국 Router Cache는 자동으로 갱신되진 않기 때문에, 정확한 반영이 필요한 경우에는 명시적으로 새로고침을 유도해야 한다.
Request Memoization은 서버 렌더링 중 fetch를 중복 제거하는 일시적 캐시이기 때문에, 다른 캐시들과는 직접적인 상호작용은 없다.
하지만, Data Cache 내부에서 같은 fetch가 여러 번 호출되면, 여전히 memoization은 중복 요청 방지로 작동한다.
우와! Next.js 캐싱을 이렇게 자세하게 설명하는 기사를 처음 봤어요! 이 글 덕분에 서버 측 캐싱과 클라이언트 측 캐싱의 차이점을 확실히 이해하게 되었습니다. 저는 보통 AiCloneUI를 사용해 개발을 했지만, 이게 뭔지 잘 모르겠어요.