Next.js App Router를 쓰다 보면 서버에서 가져온 데이터를 캐시하고, 필요한 순간에만 다시 갱신해야 하는 상황이 자주 생깁니다. 게시글 목록, 상품 재고, 공지사항처럼 “항상 새로 가져오면 비싸지만, 바뀌었을 때는 빨리 반영되어야 하는 데이터”가 대표적입니다.
이때 자주 등장하는 개념이 revalidate, revalidateTag, updateTag입니다. 이름은 비슷하지만 역할은 다릅니다. 핵심은 시간 기준으로 오래된 데이터를 갱신할 것인지, 태그 기준으로 특정 데이터만 무효화할 것인지, 사용자 액션 직후 바로 새 값을 보장할 것인지입니다.
revalidate는 시간 기반 캐시 정책이다
revalidate: 60은 캐시된 데이터를 60초 동안 재사용하겠다는 뜻입니다. 이 시간 안에는 같은 데이터를 다시 계산하거나 다시 요청하지 않고 캐시를 사용합니다. 시간이 지난 뒤 다음 요청이 들어오면 데이터를 다시 검증하거나 새로 가져옵니다.
export async function getPosts() {
const res = await fetch('https://example.com/api/posts', {
next: { revalidate: 60 },
});
return res.json();
}
이 방식은 “최대 60초 정도 늦어도 괜찮다”는 정책에 잘 맞습니다. 예를 들어 방문자가 많은 목록 페이지나 변경 주기가 느린 콘텐츠에 적합합니다.
revalidateTag는 태그 기준으로 stale 처리한다
revalidateTag('posts', 'max')는 posts 태그가 붙은 캐시 항목을 stale 상태로 표시합니다. 중요한 점은 이것이 모든 데이터를 즉시 다시 계산한다는 뜻은 아니라는 점입니다.
Next.js 공식 문서 기준으로 profile="max"를 사용하는 방식은 stale-while-revalidate에 가깝습니다. 다음 방문자에게는 기존 캐시를 먼저 보여주고, 백그라운드에서 새 데이터를 가져와 이후 요청에 반영합니다.
import { revalidateTag } from 'next/cache';
export async function POST(request: Request) {
const body = await request.json();
await savePost(body);
revalidateTag('posts', 'max');
return Response.json({ ok: true });
}
이 방식은 CMS 웹훅, 관리자 API, 외부 시스템 변경처럼 React의 사용자 액션 흐름 밖에서 데이터가 바뀌는 경우에 잘 맞습니다.
cacheTag로 캐시에 이름표를 붙인다
태그 기반 무효화를 쓰려면 먼저 캐시 데이터에 태그를 붙여야 합니다. fetch에 next.tags를 붙일 수도 있고, 캐시 함수 안에서 cacheTag를 사용할 수도 있습니다.
import { cacheTag } from 'next/cache';
export async function getPosts() {
'use cache';
cacheTag('posts');
const res = await fetch('https://example.com/api/posts', {
next: { revalidate: 60 },
});
return res.json();
}
태그는 너무 넓게 잡으면 불필요한 무효화가 많아지고, 너무 잘게 쪼개면 관리가 어려워집니다. 예를 들어 게시글 목록은 posts, 개별 게시글은 post-${id}처럼 경계를 나누는 방식이 실무에서 이해하기 쉽습니다.
updateTag는 Server Action에서 즉시 반영할 때 쓴다
updateTag는 revalidateTag와 달리 Server Action 전용입니다. 사용자가 글을 작성하거나 수정한 직후, 같은 흐름에서 바로 새 데이터를 보고 싶을 때 사용합니다.
'use server';
import { updateTag } from 'next/cache';
import { redirect } from 'next/navigation';
export async function createPost(formData: FormData) {
const title = String(formData.get('title') ?? '');
const post = await savePost({ title });
updateTag('posts');
updateTag(`post-${post.id}`);
redirect(`/posts/${post.id}`);
}
이 경우 사용자는 방금 작성한 글이 목록이나 상세 화면에 바로 반영되기를 기대합니다. 그래서 updateTag는 read-your-own-writes, 즉 “내가 방금 쓴 값은 내가 바로 읽을 수 있어야 한다”는 상황에 맞습니다.
언제 무엇을 선택해야 할까
단순히 “몇 초마다 갱신하면 된다”면 revalidate가 적합합니다. 데이터 변경 시점을 알 수 있고, 다음 방문부터 자연스럽게 새로 고치면 된다면 revalidateTag('tag', 'max')가 좋습니다.
반대로 사용자가 폼을 제출한 직후 화면에 새 값이 바로 보여야 한다면 updateTag를 고려해야 합니다. 이때 Route Handler에 updateTag를 억지로 넣으려 하면 API의 의도와 맞지 않습니다. Route Handler나 웹훅에서는 revalidateTag, Server Action의 사용자 액션 흐름에서는 updateTag로 나누는 편이 안전합니다.
정리
Next.js 캐시 전략은 “빠르게 보여줄 것인가”와 “얼마나 즉시 정확해야 하는가” 사이의 선택입니다.
revalidate는 시간 기반 정책입니다. revalidateTag는 특정 태그의 캐시를 다음 요청부터 갱신하도록 표시합니다. updateTag는 Server Action에서 사용자 액션 직후 새 데이터를 즉시 읽도록 만드는 도구입니다.
실무에서는 먼저 질문을 바꾸는 것이 좋습니다. “이 데이터는 몇 초 늦어도 되는가?”, “변경 트리거가 사용자 액션인가 외부 웹훅인가?”, “방금 쓴 데이터를 같은 흐름에서 바로 보여줘야 하는가?” 이 세 가지를 답하면 어떤 API를 써야 할지 훨씬 명확해집니다.
참고 자료
'React,Next' 카테고리의 다른 글
| React Hydration과 Selective Hydration 쉽게 이해하기 (0) | 2026.07.30 |
|---|---|
| Next.js loading.tsx와 Suspense 차이 이해하기 (0) | 2026.07.29 |
| Next.js Server Component와 Client Component 기준 (0) | 2026.07.27 |
| React 렌더링 비용과 브라우저 렌더링 비용의 차이 (0) | 2026.07.20 |
| React Context와 useSyncExternalStore 차이 이해하기 (0) | 2026.07.19 |
