Ch 02. 신선함의 기준 — force-cache, no-store, revalidate
빵의 신선도
제과점에서 빵을 사는 상황을 생각해봅니다.
"이미 구워놓은 것을 주세요." — force-cache. 빨리 받을 수 있지만, 언제 구웠는지 모릅니다.
"지금 바로 구워주세요." — no-store. 항상 갓 구운 빵이지만, 기다려야 합니다.
"아침에 구운 것 중에서 1시간 이내 것만 주세요. 없으면 새로 구워주세요." — revalidate. 적당히 신선하면서도 효율적입니다.
cache: 'force-cache' — SSG처럼
빌드 시점에 캐시된 응답을 계속 사용합니다. 데이터가 변해도 재배포 전까지는 반영되지 않습니다. 옵션을 생략하면 이것이 기본값입니다.
// 파일: app/about/page.tsx
async function getCompanyInfo() {
const res = await fetch('https://api.example.com/company', {
cache: 'force-cache', // 기본값이므로 생략 가능
});
return res.json();
}
export default async function AboutPage() {
const info = await getCompanyInfo();
return (
<div>
<h1>{info.name}</h1>
<p>{info.description}</p>
</div>
);
}
```text
**언제 쓰나.** 회사 소개, 이용약관, 개인정보처리방침처럼 자주 바뀌지 않는 정보.
### `cache: 'no-store'` — SSR처럼
캐시하지 않습니다. 요청마다 서버에서 새로 데이터를 가져옵니다. 항상 최신 데이터를 보여줍니다.
```tsx
// 파일: app/feed/page.tsx
async function getLatestFeed() {
const res = await fetch('https://api.example.com/feed', {
cache: 'no-store', // 캐시하지 않음 - 항상 최신
});
return res.json();
}
export default async function FeedPage() {
const items = await getLatestFeed();
return (
<ul>
{items.map((item: { id: number; content: string }) => (
<li key={item.id}>{item.content}</li>
))}
</ul>
);
}
```text
**언제 쓰나.** 로그인 사용자의 피드, 주식 시세, 실시간 알림처럼 요청마다 달라지는 정보.
### `next: { revalidate: N }` — ISR처럼
N초 동안은 캐시된 응답을 사용합니다. N초가 지난 후 첫 번째 요청이 오면 백그라운드에서 데이터를 새로 가져오고, 그 사이에는 이전 데이터를 계속 사용합니다.
```tsx
// 파일: app/blog/page.tsx
async function getBlogPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 }, // 1시간마다 재검증
});
return res.json();
}
export default async function BlogPage() {
const posts = await getBlogPosts();
return (
<div>
{posts.map((post: { id: number; title: string; excerpt: string }) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
);
}
```text
**언제 쓰나.** 블로그 목록, 제품 목록, 공지사항처럼 가끔 바뀌는 정보. 실시간성보다 성능이 중요한 경우.
### 세 가지 비교
| 옵션 | 캐시 동작 | 렌더링 방식과 유사 | 사용 상황 |
|------|----------|-----------------|---------|
| `force-cache` | 영구 캐시 | SSG | 거의 안 바뀌는 정보 |
| `next: { revalidate: N }` | N초마다 갱신 | ISR | 가끔 바뀌는 정보 |
| `no-store` | 캐시 없음 | SSR | 항상 최신 필요 |
### 페이지 단위로 동적 설정하기
개별 `fetch`가 아니라 페이지 전체에 설정을 적용할 수도 있습니다.
```tsx
// 파일: app/dashboard/page.tsx
// 이 페이지의 모든 fetch는 캐시하지 않습니다
export const dynamic = 'force-dynamic'; // = cache: 'no-store'
// 또는
export const revalidate = 60; // 이 페이지의 모든 fetch를 60초마다 재검증
다음 챕터에서는
다음 챕터에서는 특정 이벤트가 발생했을 때 캐시를 즉시 무효화하는 방법을 살펴봅니다. 게시글을 수정했을 때 목록 페이지가 바로 갱신되게 하려면 어떻게 해야 할까요.