Devliner

Next.js 글을 수정했는데 이전 내용이 보일 때 revalidatePath와 revalidateTag 선택법

Next.js 글을 수정했는데 이전 내용이 보일 때 revalidatePath와 revalidateTag 선택법 대표 이미지

도입: revalidatePath와 revalidateTag 선택법 정의

Next.js 글을 수정했는데 이전 내용이 보일 때 무엇을 선택해야 할까? 특정 페이지나 레이아웃을 갱신하려면 revalidatePath를, 같은 데이터를 사용하는 여러 화면을 태그 기준으로 갱신하려면 revalidateTag를 선택한다. revalidatePath는 특정 경로의 캐시를 무효화하고, revalidateTag는 여러 페이지에 걸쳐 공통으로 사용하는 데이터의 캐시를 태그 기준으로 무효화한다. 선택 기준은 무효화 범위가 단일 경로인지, 여러 페이지에 걸친 데이터 의존성인지에 따라 결정된다.

revalidatePath 핵심 항목

정의

revalidatePath는 Server Action이나 Route Handler 내에서 호출하여 특정 경로의 캐시를 즉시 무효화하는 함수다. Next.js 공식 문서에 따르면 이 함수는 App Router의 Server Component 캐시와 Data Cache를 모두 제거한다.

원리

Next.js는 빌드 시점에 정적 페이지를 생성하고 브라우저와 CDN에 캐시한다. revalidatePath를 호출하면 해당 경로의 캐시 엔트리가 삭제되고, 다음 요청 시 페이지가 새로 렌더링된다. 경로 타입 파라미터로 'page'(기본값)를 지정하면 해당 경로만, 'layout'을 지정하면 해당 레이아웃과 하위 모든 경로를 무효화한다.

예시

import { revalidatePath } from 'next/cache'

export async function updatePost(id: string, data: PostData) {
  await db.post.update({ where: { id }, data })
  revalidatePath(`/posts/${id}`)
}

블로그 글 하나를 수정한 뒤 해당 글의 상세 페이지만 갱신하려면 위처럼 특정 경로를 인자로 전달한다. 목록 페이지도 함께 갱신하려면 revalidatePath('/posts', 'layout')처럼 레이아웃 타입으로 호출하여 /posts 아래 모든 경로를 무효화할 수 있다.

오해

revalidatePath를 호출해도 브라우저 캐시는 즉시 갱신되지 않는다. 사용자가 페이지를 새로고침하거나 다시 방문해야 서버에서 새로 렌더링된 페이지를 받는다. 또한 revalidatePath는 Server Action이나 Route Handler 내부에서만 호출 가능하며, 클라이언트 컴포넌트에서 직접 실행할 수 없다.

revalidateTag 핵심 항목

정의

revalidateTag는 캐시된 데이터에 지정한 태그를 기준으로 여러 페이지의 데이터를 재검증하는 함수다. 현재 권장 형태인 revalidateTag('posts', 'max')는 항목을 stale 상태로 표시하고 다음 방문 때 stale-while-revalidate 방식으로 새 데이터를 가져온다. Next.js 공식 문서는 이를 "on-demand revalidation"의 한 방법으로 설명한다.

원리

Next.js Data Cache는 fetch 요청마다 태그를 붙일 수 있다. 동일한 API 엔드포인트나 데이터베이스 쿼리 결과를 여러 페이지에서 사용할 때, 해당 데이터에 태그를 지정해두면 revalidateTag 호출 한 번으로 그 데이터를 참조하는 모든 페이지의 캐시를 무효화할 수 있다.

예시

// 데이터 fetch 시 태그 지정
export async function getPost(id: string) {
  const res = await fetch(`https://api.example.com/posts/${id}`, {
    next: { tags: ['posts'] }
  })
  return res.json()
}

// Server Action에서 태그 기반 무효화
import { revalidateTag } from 'next/cache'

export async function updatePost(id: string, data: PostData) {
  await db.post.update({ where: { id }, data })
  revalidateTag('posts', 'max')
}

여러 페이지(목록, 상세, 관련 글)가 'posts' 태그를 공유하는 데이터를 사용한다면, revalidateTag('posts', 'max') 호출 한 번으로 모든 관련 페이지의 캐시가 무효화된다.

오해

revalidateTag는 fetch나 unstable_cache에 태그를 지정하지 않으면 아무 효과가 없다. 정적 생성된 페이지 자체에 태그를 붙이는 것이 아니라, 페이지가 의존하는 데이터 요청에 태그를 붙여야 한다. 또한 태그 이름은 대소문자를 구분하므로 일관된 네이밍 규칙이 필요하다.

핵심 원리 정리

revalidatePath와 revalidateTag는 무효화 단위가 다르다. revalidatePath는 경로 문자열을 기준으로 특정 URL의 캐시를 제거하며, 단일 페이지나 레이아웃 하위 전체를 대상으로 한다. revalidateTag는 데이터 의존성을 기준으로 여러 페이지에 흩어진 캐시를 한 번에 제거한다.

선택 기준은 다음과 같다.

  • 단일 페이지만 수정되었고 다른 페이지에 영향이 없다면 revalidatePath를 사용한다.
  • 수정한 데이터가 여러 페이지(목록, 상세, 관련 항목)에 표시된다면 revalidateTag를 사용한다.
  • 특정 섹션 전체를 갱신하려면 revalidatePath에 'layout' 타입을 지정한다.
  • 데이터 소스가 외부 API이고 여러 경로에서 동일한 엔드포인트를 호출한다면 fetch 태그와 revalidateTag 조합이 효율적이다.

Next.js 공식 문서는 두 함수를 함께 사용할 수도 있다고 명시한다. 예를 들어 글 수정 시 해당 글 상세 페이지는 revalidatePath로, 관련된 모든 목록 페이지는 revalidateTag로 무효화하는 방식이다.

흔한 오해

클라이언트에서 직접 호출 가능하다

revalidatePath와 revalidateTag는 서버 전용 함수다. 클라이언트 컴포넌트에서 직접 import하면 빌드 에러가 발생한다. Server Action을 정의하고 클라이언트에서 해당 액션을 호출하는 방식으로 사용해야 한다.

호출 즉시 모든 사용자 브라우저가 갱신된다

Route Handler에서 호출한 revalidatePath는 다음 방문 때 재검증하도록 표시한다. Server Action에서 현재 보고 있는 경로를 대상으로 호출하면 UI가 즉시 갱신될 수 있으므로, 호출 위치에 따라 사용자에게 보이는 시점이 다르다. 실시간 갱신이 필요하다면 WebSocket이나 Server-Sent Events 같은 별도 메커니즘이 필요하다.

revalidatePath만 있으면 충분하다

경로 기반 무효화는 페이지 단위로만 작동한다. 하나의 데이터 변경이 여러 경로에 영향을 준다면 각 경로마다 revalidatePath를 반복 호출해야 하는데, 이는 유지보수가 어렵고 누락 위험이 크다. 태그 기반 무효화는 데이터 중심으로 의존성을 관리하므로 확장성이 높다.

태그를 지정하지 않아도 revalidateTag가 작동한다

revalidateTag는 fetch나 unstable_cache 호출 시 명시적으로 지정한 태그만 인식한다. 태그를 붙이지 않은 데이터 요청은 revalidateTag 호출에 반응하지 않으며, 해당 페이지는 revalidatePath로만 무효화할 수 있다.

이해 확인 요약

  • revalidatePath는 특정 경로의 캐시를 무효화하며, 단일 페이지나 레이아웃 하위 전체를 대상으로 한다.
  • revalidateTag는 데이터 요청에 붙인 태그를 기준으로 여러 페이지의 캐시를 한 번에 무효화한다.
  • 선택 기준은 무효화 범위가 경로 단위인지 데이터 의존성 단위인지에 따라 결정된다.
  • 두 함수는 Server Function 또는 Route Handler에서 호출하며, revalidateTag는 현재 권장되는 profile='max' 동작을 함께 지정한다.
  • fetch나 unstable_cache에 태그를 지정하지 않으면 revalidateTag는 효과가 없다.

참고 자료

  • Nextjs
  • 글을
  • 수정했는데

관련 글

전체 보기