← 전체 글로 돌아가기

Next.js

Next.js 캐시 때문에 수정한 글이 안 바뀌어 보였던 날

Next.js에는 여러 단계의 캐시가 있다. 한 곳만 지워선 안 되는 이유를 알아보자.

Next.js 블로그를 운영하면서 글을 수정했는데 배포 후에도 여전히 예전 글이 보였던 경험이 있다. Next.js의 캐시 레이어를 제대로 이해하지 못했기 때문이다.

Next.js의 여러 캐시 레이어들

Next.js 앱에서는 여러 단계에서 캐시가 일어난다:

  1. Request Memoization - 단일 요청 내 함수 결과 캐시
  2. Data Cache - 데이터베이스/API 응답 캐시 (배포까지 유지)
  3. Full Route Cache - 빌드 시점의 정적 HTML 캐시
  4. Router Cache - 브라우저의 클라이언트 사이드 캐시
  5. CDN/Proxy Cache - Vercel 등에서의 엣지 캐시

각 캐시를 비우는 방법

// 1. 개별 데이터 캐시 재검증 (Incremental Static Regeneration)
import { revalidatePath } from 'next/cache';

export async function updatePost(id: string, data: PostData) {
  // 데이터베이스 업데이트
  const post = await db.posts.update({ where: { id }, data });

  // 이 경로의 캐시만 재생성
  revalidatePath(`/posts/${id}`);

  return post;
}

블로그 글 수정 시 체크리스트

실제로 글을 수정할 때는 여러 곳을 확인해야 한다.

# 1. 로컬 개발에서 확인
npm run dev
# localhost:3000/posts/[id]에서 변경사항 확인

# 2. 로컬에서 프로덕션 빌드로 테스트
npm run build
npm run start

# 3. 실제 배포 후 브라우저 캐시 비우기
# Chrome DevTools > Network > "Disable cache" 체크
# 또는 Cmd+Shift+R (Mac) / Ctrl+Shift+R (Windows)

# 4. CDN 캐시 확인
curl -I https://example.com/posts/[id]
# Cache-Control 헤더 확인

Next.js 설정에서 캐시 제어하기

// app/posts/[id]/page.tsx
import type { Metadata } from 'next';

export const revalidate = 3600; // 1시간마다 재검증

export async function generateMetadata({
  params,
}: Props): Promise<Metadata> {
  const post = await getPost(params.id);
  return {
    title: post.title,
    description: post.excerpt,
  };
}

export default async function PostPage({ params }: Props) {
  const post = await getPost(params.id);
  return <article>{post.content}</article>;
}

개발과 운영의 캐시 차이

// lib/get-post.ts
const CACHE_DURATION = process.env.NODE_ENV === 'production' ? 3600 : 0;

export async function getPost(id: string) {
  return await db.posts.findUnique(
    { where: { id } },
    { cache: CACHE_DURATION > 0 ? CACHE_DURATION : 'no-cache' }
  );
}

긴급한 수정 후 캐시 강제 비우기

이미 배포된 글에서 오류를 발견했다면:

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

export async function POST(request: Request) {
  const secret = request.headers.get('x-revalidate-secret');

  if (secret !== process.env.REVALIDATE_SECRET) {
    return new Response('Unauthorized', { status: 401 });
  }

  const tag = request.headers.get('x-revalidate-tag');
  revalidateTag(tag); // 예: 'posts'

  return Response.json({ revalidated: true });
}

Next.js의 캐시 메커니즘을 이해하면 예상 밖의 "왜 안 바뀌어?" 상황을 피할 수 있다.