Next.js 16 캐싱 설명: 재검증, 태그, 드래프트 모드 및 실제 프로덕션 패턴

발행: (2026년 2월 22일 오후 07:12 GMT+9)
16 분 소요
원문: Dev.to

Source: Dev.to

Next.js 16 캐싱 설명: 재검증, 태그, Draft Mode, 실제 프로덕션 패턴

Next.js 16에서는 데이터 캐싱과 재검증을 다루는 새로운 API와 개념이 도입되었습니다. 이 글에서는 revalidation, tags, draft mode 그리고 실제 프로젝트에서 사용할 수 있는 패턴들을 자세히 살펴보겠습니다.


목차

  1. 왜 캐싱이 중요한가?
  2. Revalidation (재검증)
  3. Tags (태그)
  4. Draft Mode (드래프트 모드)
  5. 실제 프로덕션 패턴
  6. 마무리

왜 캐싱이 중요한가?

  • 성능: 서버에서 데이터를 매번 가져오는 대신, 이미 캐시된 데이터를 재사용함으로써 응답 시간을 크게 단축할 수 있습니다.
  • 비용 절감: 외부 API 호출을 최소화하면 비용을 절감하고, 레이트 제한(rate limits) 문제도 피할 수 있습니다.
  • UX: 사용자는 더 빠른 페이지 로딩을 경험하게 되며, 이는 전환율과 SEO에 긍정적인 영향을 줍니다.

Revalidation (재검증)

기본 개념

revalidate 옵션은 ISR (Incremental Static Regeneration) 과 비슷하지만, Next.js 16에서는 fetchcache API를 통해 더 직관적으로 사용할 수 있습니다.

// 예시: 페이지 컴포넌트에서 fetch 사용
export default async function Page() {
  const data = await fetch('/api/posts', {
    // 60초마다 백그라운드에서 재검증
    next: { revalidate: 60 }
  }).then(res => res.json());

  return <PostsList posts={data} />;
}
  • revalidate: 6060초가 지나면 백그라운드에서 새 데이터를 가져와 캐시를 업데이트합니다.
  • 사용자는 stale(구버전) 데이터를 즉시 보게 되며, 새 데이터는 다음 요청에 반영됩니다.

강제 재검증

특정 상황(예: CMS에서 콘텐츠가 업데이트된 경우)에는 강제 재검증이 필요합니다.

// 서버 액션 또는 API 라우트에서
export async function POST(request: Request) {
  // 데이터베이스 업데이트 로직 ...

  // 캐시 무효화
  await revalidatePath('/posts');
  return new Response('OK');
}
  • revalidatePath는 지정된 경로의 캐시를 즉시 무효화합니다.
  • revalidateTag와 조합하면 더 세밀한 제어가 가능합니다.

Tags (태그)

태그란?

태그는 데이터 의존성을 선언하는 메커니즘입니다. 여러 페이지가 같은 데이터를 공유할 때, 해당 데이터가 변경되면 연관된 모든 페이지를 동시에 무효화할 수 있습니다.

사용 예시

// posts/[id]/page.tsx
export default async function Post({ params }) {
  const post = await fetch(`/api/posts/${params.id}`, {
    next: { tags: ['post'] }   // 이 페이지는 'post' 태그에 의존
  }).then(res => res.json());

  return <PostDetail post={post} />;
}
// comments/[postId]/page.tsx
export default async function Comments({ params }) {
  const comments = await fetch(`/api/comments?postId=${params.postId}`, {
    next: { tags: ['comment'] } // 이 페이지는 'comment' 태그에 의존
  }).then(res => res.json());

  return <CommentsList comments={comments} />;
}

태그 무효화

// API 라우트: 댓글이 추가될 때
export async function POST(request: Request) {
  // 댓글 저장 로직 ...

  // 'comment' 태그에 연결된 모든 캐시 무효화
  await revalidateTag('comment');
  return new Response('Comment added');
}
  • revalidateTag('comment')를 호출하면 댓글과 연관된 모든 페이지가 재검증됩니다.
  • 이렇게 하면 데이터 일관성을 손쉽게 유지할 수 있습니다.

Draft Mode (드래프트 모드)

개념

Draft Mode는 프리뷰(preview) 환경을 제공하여, 아직 퍼블리시되지 않은 콘텐츠를 실시간으로 확인할 수 있게 해줍니다. 기존 next/preview와 달리, 서버 액션fetch를 그대로 사용할 수 있습니다.

활성화 / 비활성화

// app/api/draft/activate/route.ts
import { draftMode } from 'next/headers';

export async function GET() {
  draftMode().enable(); // Draft Mode 켜기
  return new Response('Draft mode enabled');
}

// app/api/draft/deactivate/route.ts
import { draftMode } from 'next/headers';

export async function GET() {
  draftMode().disable(); // Draft Mode 끄기
  return new Response('Draft mode disabled');
}

Draft Mode와 캐시

Draft Mode가 활성화된 요청은 캐시를 건너뛰고 최신 데이터를 직접 조회합니다.

export default async function Page() {
  const data = await fetch('/api/posts', {
    // Draft Mode가 켜져 있으면 캐시 무시
    next: { revalidate: 0 }
  }).then(res => res.json());

  return <PostsList posts={data} />;
}
  • revalidate: 0no‑cache를 의미합니다.
  • 일반 사용자에게는 캐시된 버전이 제공되고, 프리뷰 사용자에게는 최신(드래프트) 버전이 제공됩니다.

실제 프로덕션 패턴

1️⃣ 데이터 레이어와 캐시 전략 분리

  • API 라우트: 모든 데이터 로직을 여기서 처리하고, revalidateTag/revalidatePath를 사용해 캐시를 관리합니다.
  • 페이지/컴포넌트: fetch(..., { next: { tags, revalidate } })만 선언해 의존성을 명시합니다.

2️⃣ 태그 기반 무효화 + 재검증 조합

// 게시글 업데이트 API
export async function PUT(request: Request) {
  // DB 업데이트 ...

  // 게시글과 댓글 모두 무효화
  await Promise.all([
    revalidateTag('post'),      // 게시글 페이지
    revalidateTag('comment')   // 댓글 리스트
  ]);

  return new Response('Post updated');
}
  • 핵심: 하나의 업데이트가 여러 캐시 엔트리를 동시에 무효화하도록 설계합니다.

3️⃣ ISR + Draft Mode 혼합

// app/posts/[slug]/page.tsx
export default async function Post({ params }) {
  const isDraft = draftMode().isEnabled;
  const data = await fetch(`/api/posts/${params.slug}`, {
    next: {
      // Draft Mode이면 캐시 안 함, 아니면 30초마다 재검증
      revalidate: isDraft ? 0 : 30,
      tags: ['post']
    }
  }).then(res => res.json());

  return <PostDetail post={data} />;
}
  • 일반 방문자는 ISR(30초)으로 빠른 응답을 받고, 에디터는 실시간으로 최신 초안을 확인합니다.

4️⃣ Edge Middleware와 캐시 헤더

Edge Middleware에서 Cache-Control 헤더를 직접 조작해 CDN 레벨 캐시 정책을 세밀하게 제어할 수 있습니다.

// middleware.ts
import { NextResponse } from 'next/server';

export function middleware(request) {
  const response = NextResponse.next();

  // API 경로는 5초 동안 CDN에 캐시
  if (request.nextUrl.pathname.startsWith('/api/')) {
    response.headers.set('Cache-Control', 'public, max-age=5, stale-while-revalidate=30');
  }

  return response;
}
  • 이렇게 하면 서버CDN 양쪽에서 캐시를 최적화할 수 있습니다.

마무리

Next.js 16의 캐싱 시스템은 재검증, 태그, Draft Mode라는 세 가지 핵심 요소를 중심으로 설계되었습니다.

기능언제 사용하나요?주요 API
Revalidation일정 주기로 데이터를 최신 상태로 유지하고 싶을 때fetch(..., { next: { revalidate } }), revalidatePath
Tags여러 페이지가 동일 데이터를 공유하고, 해당 데이터가 바뀔 때 전체를 무효화하고 싶을 때next: { tags }, revalidateTag
Draft Mode아직 퍼블리시되지 않은 콘텐츠를 프리뷰하고 싶을 때draftMode().enable()/disable(), draftMode().isEnabled

이 세 가지를 조합하면 성능데이터 일관성을 동시에 만족하는 강력한 프로덕션 패턴을 만들 수 있습니다. 실제 프로젝트에 적용해 보면서, 캐시 무효화 전략을 점진적으로 다듬어 가길 권장합니다.

Tip: 캐시 전략을 설계할 때는 데이터 변경 빈도, 사용자 트래픽 패턴, 외부 API 비용을 모두 고려하세요. 적절한 revalidate 간격과 태그 설계가 장기적인 유지보수 비용을 크게 낮춰줍니다.

🎯 우리가 만들고 있는 것

  • 정적 + 동적 제어를 fetch 로 사용
  • 태그 기반 무효화
  • 요청 시 재검증
  • 미리보기 워크플로우를 위한 초안 모드
  • 제가 실제로 사용하는 실전 패턴

추측은 없습니다. 실수로 오래된 페이지가 없습니다.

🧠 첫 번째: 새로운 사고 모델

Next.js 16에서는 캐싱이 더 이상 “페이지 기반”이 아닙니다.
이제는 데이터 기반입니다. 캐싱 단위는 이제 fetch() 호출이 됩니다.

  • fetch는 캐시되거나 동적으로 동작할 수 있습니다.
  • fetch는 재검증 규칙을 정의할 수 있습니다.
  • fetch는 태그를 통해 무효화될 수 있습니다.

이렇게 하면 더 깔끔하고 확장성이 높아집니다.

🔥 1. fetch 로 캐시 제어하기

기본 동작

const res = await fetch("https://api.example.com/posts");

기본적으로 이 응답은 프로덕션 환경에서 캐시됩니다.

재검증이 있는 정적 (ISR‑스타일)

const res = await fetch("https://api.example.com/posts", {
  next: { revalidate: 60 }
});
  • 이 응답을 캐시합니다.
  • 매 60 초마다 재검증합니다.

이전 ISR 패턴을 보다 세밀한 단위로 대체합니다.

완전 동적 (캐시 없음)

const res = await fetch("https://api.example.com/posts", {
  cache: "no-store"
});

동적 렌더링을 강제합니다. 다음과 같은 경우에 사용합니다:

  • 사용자별 데이터
  • 인증된 대시보드
  • 빠르게 변하는 메트릭

🏷️ 2. 실제 업그레이드: 캐시 태그

Next.js 16에서는 캐시된 fetch에 태그를 지정할 수 있습니다:

const res = await fetch("https://api.example.com/posts", {
  next: { tags: ["posts"] }
});

이제 캐시 엔트리는 "posts" 태그와 연결되어 수동 무효화를 가능하게 합니다.

🚀 3. 태그를 이용한 온‑디맨드 재검증

태그를 재검증하는 라우트 핸들러를 생성합니다:

// app/api/revalidate/route.ts
import { revalidateTag } from "next/cache";

export async function POST() {
  revalidateTag("posts");
  return Response.json({ revalidated: true });
}

이 엔드포인트를 호출하면 "posts" 태그가 붙은 모든 캐시된 fetch가 즉시 무효화됩니다—정밀하게, 페이지 수준이 아니라, 전역이 아닙니다.

🧪 4. 재검증 + 태그 결합 (최고 패턴)

const res = await fetch("https://api.example.com/posts", {
  next: {
    revalidate: 3600,
    tags: ["posts"]
  }
});
  • 자동 시간당 새로 고침
  • 필요 시 수동 무효화
  • 불필요한 재빌드 없음

📝 5. 미리보기 워크플로우를 위한 Draft Mode

Draft Mode 활성화

import { draftMode } from "next/headers";

export async function GET() {
  draftMode().enable();
  return Response.redirect("/admin");
}

페이지에서 Draft Mode 사용

import { draftMode } from "next/headers";

export default async function Page() {
  const { isEnabled } = draftMode();

  const res = await fetch("https://api.example.com/posts", {
    cache: isEnabled ? "no-store" : "force-cache"
  });

  const data = await res.json();

  return { data.title };
}

Draft Mode가 활성화되면:

  • 캐시가 우회됩니다
  • 미발행된 변경 사항이 표시됩니다

비활성화되면 일반 캐시가 재개됩니다.

⚙️ 6. 실제로 사용하는 프로덕션 패턴

사용 사례캐시 구성
공개 콘텐츠next: { revalidate: 600, tags: ["posts"] }
관리자 업데이트revalidateTag("posts")
사용자 대시보드cache: "no-store"
프리뷰 라우트draftMode + no-store

Result: 성능, 신선도, 정밀도, 확장성.

⚠️ 내가 저지른 흔한 실수

  • cache: "no-store"revalidate를 혼용하기
  • 태그를 빼먹고 전체 경로를 재검증하려 시도하기
  • 개발 모드가 프로덕션 캐싱을 반영한다고 가정하기
  • 과도하게 무효화하기

Tip: 개발 모드는 다르게 동작합니다. 항상 프로덕션 빌드에서 캐싱을 테스트하세요:

next build
next start

🧩 이것이 모든 것을 바꾸는 방법

Next.js 16 이전에는 캐싱이 페이지 기반이고 간접적으로 느껴졌습니다.
이제는:

  • 선언적
  • 세분화된
  • 완전 제어 가능

페이지 ISR에서 fetch‑level 캐싱으로의 전환은 주요 아키텍처 개선입니다.

🏁 최종 생각

Next.js 16은 단순히 캐싱을 개선하는 것이 아니라 예측 가능하게 만듭니다.
다음 항목을 이해한다면:

  • fetch 캐시 제어
  • revalidate
  • tags
  • revalidateTag()
  • draftMode()

성능을 추측하는 것이 아니라 직접 제어할 수 있습니다.

이 내용이 도움이 되었다면, 오래된 데이터와 싸우는 다른 프론트엔드 엔지니어와 공유해 주세요.
Next.js 16에서 만든 흥미로운 캐싱 패턴이 있다면 보고 싶습니다.

추가 심층 분석이 곧 찾아옵니다.

Check me out at .

0 조회
Back to Blog

관련 글

더 보기 »

로드 시간을 60% 줄인 방법

Performance Optimization: Reducing Dashboard Load Time by 60 % Performance optimization은 개발자가 배울 수 있는 가장 실용적인 기술 중 하나이다…