Next.js 최신 캐싱 전략 총정리

2025. 9. 3. 15:17·Frontend Development
728x90

Next.js는 App Router 기반으로 서버 컴포넌트와 데이터 페칭을 통합하면서
캐싱 체계를 전면 재설계하였습니다. 본 글에서는 실무에서 가장 자주
사용되는 네 가지 축, 즉 Request Memoization, Client Router
Cache
, Data Cache, Full Route Cache를 중심으로 원리를
정리하고, 실행 가능한 예제와 함께 권장되는 베스트 프랙티스를 제시합니다.


목차

  • Request Memoization: 동일 요청 중복 제거
  • Client Router Cache: 탐색 성능 최적화
  • Data Cache: fetch 응답의 서버 캐싱 및 검증
  • Full Route Cache: 페이지 단위 정적 캐싱(ISR 포함)
  • 실무 팁/주의사항, 비교 표, 참고 자료

캐싱 전반 개념 맵

  • 서버 단계: Request Memoization, Data Cache, Full Route Cache
  • 클라이언트 단계: Client Router Cache(RSC Payload/라우팅)
  • 동적 여부 판별 기준: cookies(), headers(), searchParams,
    dynamic/revalidate 설정
  • 무효화 수단: revalidate, revalidatePath, revalidateTag,
    router.refresh()

1) Request Memoization (요청 중복 제거)

동일한 입력으로 fetch를 여러 컴포넌트에서 호출하더라도 서버 렌더링 한
사이클 내에서는 단 한 번만 네트워크 요청이 발생하며, 그 결과가
공유됩니다. 이는 네트워크 비용과 백엔드 부하를 크게 줄여줍니다.

  • 범위: 단일 서버 렌더(요청) 사이클
  • 효과: 동일 URL+옵션의 fetch 중복 제거
  • 주의사항: cache: 'no-store'일 경우에도 동일 렌더 내에서는 중복
    제거가 적용됨 (요청 간 캐싱은 아님)

실행 예시

동일 데이터를 여러 컴포넌트에서 사용하더라도 네트워크는 1회만
발생합니다.

// app/posts/[id]/page.tsx
import { Suspense } from 'react';

async function getPost(id: string) {
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
    cache: 'no-store'
  });
  return res.json() as Promise<{ id: number; title: string; body: string }>
}

async function PostTitle({ id }: { id: string }) {
  const post = await getPost(id)
  return <h2>{post.title}</h2>
}

async function PostBody({ id }: { id: string }) {
  const post = await getPost(id)
  return <p>{post.body}</p>
}

export default async function Page({ params }: { params: { id: string } }) {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <PostTitle id={params.id} />
        <PostBody id={params.id} />
      </Suspense>
    </div>
  )
}

2) Client Router Cache (클라이언트 라우터 캐시)

브라우저 메모리에 RSC Payload와 레이아웃/로딩 상태 등을 캐싱하여 탐색
속도를 향상시킵니다. next/link의 사전 불러오기(prefetch) 기능과
연계되어 페이지 전환이 즉각적으로 이루어집니다.

  • 저장 위치: 브라우저 메모리(세션 범위와 유사)
  • 채워지는 시점: 링크 마우스 오버, 뷰포트 노출, 명시적
    router.prefetch()
  • 무효화 조건: 서버에서 경로/태그 재검증, 클라이언트의
    router.refresh() 호출

실행 예시

목록 페이지에서 상세 링크를 사전 불러오고, 변경 시 캐시를 무효화합니다.

// app/articles/page.tsx
import Link from 'next/link'

export default function ArticlesPage() {
  const items = Array.from({ length: 5 }).map((_, i) => ({ id: i + 1, title: `Post ${i + 1}` }))
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>
          <Link href={`/articles/${item.id}`}>{item.title}</Link>
        </li>
      ))}
    </ul>
  )
}

변경 후에는 서버 액션과 함께 revalidatePath 또는 클라이언트의
router.refresh()를 호출하여 동기화를 보장합니다.


3) Data Cache (데이터 응답 캐시)

서버에서 실행되는 fetch 응답을 캐싱합니다. 기본 동작은 렌더링 모드 및
옵션에 따라 달라집니다.

  • 기본 규칙 요약:
    • 정적 렌더링 경로에서는 GET 요청이 기본적으로 캐싱
      대상(force-cache 유사)
    • 동적 렌더링을 유발하는 경우에는 기본값이 no-store
    • 명시적으로 next: { revalidate: number }를 지정하면 ISR처럼
      특정 주기로 재검증

실행 예시

// app/products/page.tsx
export const revalidate = 60

async function getProducts() {
  const res = await fetch('https://api.example.com/products', {
    cache: 'force-cache',
    next: { revalidate: 120, tags: ['products'] }
  })
  if (!res.ok) throw new Error('Failed to load')
  return res.json() as Promise<Array<{ id: string; name: string }>>
}

export default async function ProductsPage() {
  const products = await getProducts()
  return (
    <div>
      <h2>Products</h2>
      <ul>
        {products.map((p) => (
          <li key={p.id}>{p.name}</li>
        ))}
      </ul>
    </div>
  )
}

태그 단위 무효화:

// app/admin/actions.ts
'use server'
import { revalidateTag } from 'next/cache'

export async function createProduct(name: string) {
  await fetch('https://api.example.com/products', {
    method: 'POST',
    body: JSON.stringify({ name })
  })
  revalidateTag('products')
}

4) Full Route Cache (페이지 단위 캐싱, ISR 포함)

페이지 전체(HTML + RSC Payload)를 캐싱합니다. 이는 빌드 타임 또는 최초
요청 시 생성되며, revalidate에 따라 주기적으로 갱신됩니다.

  • 활성 조건: 동적 함수 미사용 + 정적 렌더 경로 + 데이터 캐시 가능
  • 구성 방법: export const revalidate = number 또는
    dynamic = 'force-static'
  • 비활성 조건: dynamic = 'force-dynamic', cache: 'no-store',
    동적 함수 사용

비교 표


구분 범위 저장 위치 기본 수명 명시 제어 대표 사용처


Request 단일 서버 서버 메모리 요청 내장 기능 동일 데이터
Memoization 렌더 사이클 종료까지 중복 호출
제거

Client Router 클라이언트 브라우저 세션 유사 router.refresh() 빠른 페이지
Cache 탐색 메모리 전환

Data Cache 개별 fetch 서버 캐시 옵션에 따름 cache/no-store, API 응답
응답 revalidate, tags 캐싱

Full Route 페이지 전체 서버 캐시 revalidate revalidate, 마케팅,
Cache 주기 dynamic 블로그,
카탈로그



베스트 프랙티스 및 주의사항

  • 개인화/민감 데이터는 반드시 cache: 'no-store' 설정
  • 공용 데이터는 revalidate 또는 force-cache 전략으로 관리
  • 태그 기반 무효화를 일관되게 활용 (revalidateTag)
  • 동적 함수 사용 여부를 명확히 구분하여 Full Route Cache가 불필요하게
    깨지지 않도록 관리
  • 서버 액션 이후에는 revalidatePath 및 router.refresh()로 캐시
    동기화 보장
  • 트래픽 상황에 따라 prefetch={false}로 과도한 사전 요청 제어

결론: 적용 가이드

  1. 페이지 특성을 파악하여 개인화 여부, 변경 주기, 트래픽 패턴을 분석
  2. 가능한 경우 Full Route Cache와 revalidate를 활용
  3. 데이터 단위로 force-cache/no-store/next.revalidate/태그 전략을
    수립
  4. revalidatePath, revalidateTag, router.refresh()를 통해 무효화
    경로를 설계
  5. 실제 트래픽에서 TTFB, 탐색 속도, 백엔드 QPS를 모니터링하여 검증

핵심은 "정적 가능 영역을 최대화하고, 동적 처리는 최소화"하는
것입니다. Next.js의 네 가지 캐시 축을 적절히 조합하면 성능과 일관성을
동시에 달성할 수 있습니다.

728x90
저작자표시 비영리 변경금지 (새창열림)

'Frontend Development' 카테고리의 다른 글

React Error Boundary: 왜 아직도 클래스일까?  (0) 2025.09.07
setTimeout vs Promise.then vs queueMicrotask  (0) 2025.09.05
면접에서 묻는 "의존성 주입 경험이 있나요?"의 의미  (1) 2025.08.26
useState vs useRef vs let: 언제 무엇을 써야 할까?  (0) 2025.08.21
Core Web Vitals: LCP, INP, CLS 개념과 개선 방법  (3) 2025.08.20
'Frontend Development' 카테고리의 다른 글
  • React Error Boundary: 왜 아직도 클래스일까?
  • setTimeout vs Promise.then vs queueMicrotask
  • 면접에서 묻는 "의존성 주입 경험이 있나요?"의 의미
  • useState vs useRef vs let: 언제 무엇을 써야 할까?
Kun Woo Kim
Kun Woo Kim
안녕하세요, 김건우입니다! 웹과 앱 개발에 열정적인 전문가로, React, TypeScript, Next.js, Node.js, Express, Flutter 등을 활용한 프로젝트를 다룹니다. 제 블로그에서는 개발 여정, 기술 분석, 실용적 코딩 팁을 공유합니다. 창의적인 솔루션을 실제로 적용하는 과정의 통찰도 나눌 예정이니, 궁금한 점이나 상담은 언제든 환영합니다.
  • Kun Woo Kim
    WhiteMouseDev
    김건우
  • 깃허브
    포트폴리오
    velog
  • 전체
    오늘
    어제
  • 공지사항

    • [인사말] 이제 티스토리에서도 만나요! WhiteMouse⋯
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 분류 전체보기 (133)
      • Frontend Development (62)
      • Backend Development (25)
      • Algorithm (33)
        • 백준 (11)
        • 프로그래머스 (17)
        • 알고리즘 (5)
      • Infra (1)
      • 자료구조 (3)
      • Language (6)
        • JavaScript (6)
  • 링크

    • Github
    • Portfolio
    • Velog
  • 인기 글

  • 태그

    frontend development
    tailwindcss
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Kun Woo Kim
Next.js 최신 캐싱 전략 총정리
상단으로

티스토리툴바