안녕하세요! 프론트엔드 개발자 여러분을 돕는 든든한 강사, Gemini입니다. Next.js 공식 문서를 통해 깊이 있게 공부하시려는 모습이 정말 멋지네요!
영어로 된 딱딱한 기술 문서를 읽다 보면 지치기 쉬운데요, 제가 여러분의 눈높이에 맞춰서 아주 친근한 구어체로 전부 번역해 드릴게요. 절대 대충 요약하지 않고 원본에 있는 모든 내용을 꼼꼼히 담았습니다. 게다가 제가 실무에서 겪었던 경험을 바탕으로, 공식 문서만으로는 이해하기 어려운 부분들에 살을 붙여서 부연 설명과 꿀팁까지 듬뿍 담았으니 편하게 읽어주세요! 자, 그럼 시작해볼까요?
title: revalidateTag (캐시 태그 재검증하기)
description: revalidateTag 함수에 대한 API 참조 문서입니다.
url: "https://nextjs.org/docs/app/api-reference/functions/revalidateTag"
version: 16.1.6
lastUpdated: 2026-02-27
prerequisites (선수 지식):
revalidateTag 함수는 특정 캐시 태그(cache tag)에 대해 캐시된 데이터를 여러분이 원할 때 즉시(on-demand) 무효화(invalidate)할 수 있도록 해주는 아주 유용한 녀석입니다.
이 함수는 블로그 게시물, 상품 카탈로그, 또는 지금 여러분이 보고 계신 공식 문서처럼, 업데이트 내용이 화면에 반영되는 데 약간의 지연이 발생해도 괜찮은 콘텐츠에 사용하기 딱 좋습니다. 왜냐하면 사용자들은 서버 백그라운드에서 신선한 최신 데이터가 로드되는 동안, 일단은 기존의 약간 오래된(stale) 콘텐츠를 아주 빠르게 받아볼 수 있거든요.
💡 강사의 부연 설명:
여기서 '무효화(invalidate)한다'는 말은 쉽게 말해 서버에게 "야, 이 태그가 달린 데이터는 이제 옛날 거니까 버리고 새로 가져올 준비를 해!"라고 알려주는 거예요. 만약 쇼핑몰을 만드는데 '신발'이라는 태그가 달린 상품 가격이 바뀌었다면, 이 함수 하나로 메인 페이지, 카테고리 페이지, 상세 페이지에 흩어져 있는 '신발' 데이터를 한 번에 새것으로 갱신할 수 있는 거죠!
revalidateTag는 Server Functions (서버 함수) 및 Route Handlers (라우트 핸들러) 내부에서 호출할 수 있습니다.
단, revalidateTag는 Client Components (클라이언트 컴포넌트)나 Proxy에서는 호출할 수 없습니다. 이 함수는 오로지 서버 환경에서만 작동하도록 설계되었기 때문이에요.
💡 강사의 꿀팁:
처음 Next.js의 App Router를 접하시면 종종 클라이언트 컴포넌트(버튼 클릭 이벤트 등)에서 이 함수를 직접 부르려고 하다가 에러를 만나곤 해요. 클라이언트(브라우저)는 서버의 캐시 저장소에 직접 접근할 권한이 없답니다. 반드시 Server Action을 만들어서 클라이언트에서 그 액션을 호출하는 방식으로 우회하셔야 해요!
재검증이 어떻게 동작하는지는 여러분이 이 함수에 두 번째 인자(argument)를 어떻게 넘겨주느냐에 따라 달라집니다.
profile="max"를 사용할 경우 (🌟 추천 방식!): 태그 항목이 '오래됨(stale)'으로 표시됩니다. 그리고 다음에 누군가가 해당 태그가 포함된 리소스를 방문하면, stale-while-revalidate (SWR) 방식을 사용하게 됩니다. 이게 무슨 뜻이냐면, 일단 사용자에게는 빠르게 기존의 오래된(stale) 콘텐츠를 보여주고, 그와 동시에 눈에 보이지 않는 백그라운드에서는 신선한 새 콘텐츠를 몰래 가져온다는 뜻이에요!profile="max"를 사용하시거나, 즉각적인 업데이트가 필요하다면 updateTag 함수로 넘어가시는 것을 강력히 권장합니다.알아두면 좋은 점 (Good to know):
profile="max"를 사용할 때,revalidateTag는 태그된 데이터를 그저 '오래된 것(stale)'으로 표시만 해둡니다. 실제로 신선한 새 데이터를 가져오는 작업은, 다음에 누군가가 해당 태그를 사용하는 페이지를 방문했을 때 비로소 이루어집니다. 즉,revalidateTag를 호출했다고 해서 그 즉시 수많은 재검증 작업이 한꺼번에 와르르 터지는 것이 아니라는 뜻이죠. 실제 무효화 작업은 오직 다음 방문객이 있을 때만 일어납니다. 아주 효율적이죠?
revalidateTag(tag: string, profile: string | { expire?: number }): void;
tag: 재검증하고 싶은 데이터와 연결된 캐시 태그를 나타내는 문자열(string)입니다. 이 값은 최대 256자를 넘을 수 없으며, 대소문자를 엄격하게 구분(case-sensitive)합니다.profile: 재검증이 어떻게 동작할지 지정하는 문자열입니다. 가장 추천하는 값은 stale-while-revalidate 방식을 제공하는 "max"입니다. 또는 cacheLife에 정의된 기본 프로필이나 커스텀 프로필 중 하나를 사용할 수도 있습니다. 그것도 아니라면, 커스텀 만료 동작을 위해 expire 속성을 가진 객체를 전달할 수도 있어요.이 함수를 쓰려면 당연히 먼저 캐시된 데이터에 태그가 부여되어 있어야겠죠? 태그를 부여하는 방법에는 두 가지가 있습니다:
fetch 함수의 next.tags 옵션을 사용합니다.fetch(url, { next: { tags: ['posts'] } })
'use cache' 지시어(directive)와 함께 캐시된 함수나 컴포넌트 내부에서 cacheTag 함수를 사용합니다.import { cacheTag } from 'next/cache'
async function getData() {
'use cache'
cacheTag('posts')
// ... 나머지 로직
}
알아두면 좋은 점 (Good to know): 인자를 하나만 넣는 형태인
revalidateTag(tag)는 앞서 말씀드렸듯 폐기 예정(deprecated)입니다. 현재 TypeScript 에러를 무시하면 작동하긴 하지만, 이 동작은 향후 버전에서 완전히 제거될 수 있습니다. 반드시 인자가 두 개인 형태로 코드를 업데이트하세요!
revalidateTag는 어떠한 값도 반환하지 않습니다 (void).
revalidatePath (revalidatePath와의 관계)revalidateTag가 특정 태그를 가진 데이터를 그 태그를 사용하는 모든 페이지에 걸쳐서(across all pages) 무효화한다면, revalidatePath는 특정 페이지나 레이아웃의 경로(path) 단위로 데이터를 무효화합니다.
💡 강사의 부연 설명:
예를 들어 블로그를 만든다고 생각해볼게요. 어떤 글을 수정했을 때 그 글이 보여지는 곳이 '메인 페이지', '카테고리 페이지', '작성자 프로필 페이지' 등 아주 다양하다면 경로 기반인revalidatePath로 이걸 다 지우기는 너무 벅차겠죠? 이럴 땐 모든 글 조회 데이터에 'post'라는 태그를 붙여두고revalidateTag('post', 'max')한 방이면 끝납니다.
반대로, 딱 '/about' 이라는 회사 소개 페이지 하나만 수정했고 이 데이터가 다른 곳에선 안 쓰인다면revalidatePath('/about')이 훨씬 직관적이고 편하답니다.
알아두면 좋은 점 (Good to know): 이 두 함수는 서로 다른 목적을 가지고 있으며, 데이터의 일관성을 완벽하게 유지하기 위해 종종 함께 사용해야 할 수도 있습니다. 좀 더 자세한 예시나 고려해야 할 사항들이 궁금하다면 revalidateTag와 updateTag의 관계 문서를 확인해 보세요.
아래 예시들은 다양한 상황에서 revalidateTag를 어떻게 사용하는지 보여줍니다. 두 예시 모두 데이터가 '오래됨'으로 표시되고 stale-while-revalidate 동작을 사용하도록 profile="max"를 사용하고 있습니다. 대부분의 실무 상황에서 가장 권장되는 방식이죠.
'use server'
import { revalidateTag } from 'next/cache'
export default async function submit() {
await addPost()
revalidateTag('posts', 'max')
}
'use server'
import { revalidateTag } from 'next/cache'
export default async function submit() {
await addPost()
revalidateTag('posts', 'max')
}
import type { NextRequest } from 'next/server'
import { revalidateTag } from 'next/cache'
export async function GET(request: NextRequest) {
const tag = request.nextUrl.searchParams.get('tag')
if (tag) {
revalidateTag(tag, 'max')
return Response.json({ revalidated: true, now: Date.now() })
}
return Response.json({
revalidated: false,
now: Date.now(),
message: 'Missing tag to revalidate',
})
}
import { revalidateTag } from 'next/cache'
export async function GET(request) {
const tag = request.nextUrl.searchParams.get('tag')
if (tag) {
revalidateTag(tag, 'max')
return Response.json({ revalidated: true, now: Date.now() })
}
return Response.json({
revalidated: false,
now: Date.now(),
message: 'Missing tag to revalidate',
})
}
알아두면 좋은 점 (Good to know): 웹훅(webhooks)이나 외부 서드파티 서비스에서 데이터의 즉각적인 만료가 필요할 때가 있습니다. 이럴 때는 두 번째 인자로
{ expire: 0 }을 넘겨서revalidateTag(tag, { expire: 0 })처럼 사용할 수 있습니다. 외부 시스템이 여러분의 라우트 핸들러를 호출하고 나서 데이터가 즉시 만료되어야만 하는 상황에서는 이 패턴이 꼭 필요해요. 하지만 그 외의 모든 일반적인 상황에서 화면을 즉시 업데이트하고 싶다면, Server Actions 내에서updateTag함수를 사용하는 것이 좋습니다.💡 강사의 부연 설명:
여기서 말하는 웹훅(Webhook)이란, 예를 들어 여러분이 사용하는 결제 서비스(ex: 포트원, 토스페이먼츠)나 CMS(ex: Sanity, Strapi)에서 데이터가 변경되었을 때, 그쪽 서버에서 우리 서버(Next.js)로 "데이터 바뀌었어!" 하고 알림을 쏴주는 것을 말해요. 이 알림을 라우트 핸들러로 받은 직후에는 당장 캐시를 날려버려야 하니까{ expire: 0 }을 쓰는 거죠!
모든 문서의 의미론적 개요(semantic overview)를 보시려면 https://nextjs.org/docs/sitemap.md 를 참고해 주세요.
이용 가능한 모든 문서의 색인(index)을 보시려면 https://nextjs.org/docs/llms.txt 를 참고해 주세요.
어떠신가요? 공식 문서 내용이 조금 더 입체적으로 다가오시나요? 공부하시다가 더 궁금한 점이 생기면 언제든 질문해 주세요! 추가로 Next.js의 다른 캐싱 전략에 대해서도 함께 살펴볼까요?