frontend

Next.js 캐싱의 혁명: 'use cache' 지시어 완벽 가이드 및 실무 활용법

Next.js 개발자들을 가장 괴롭혔던 주제를 하나 꼽으라면 단연 **캐싱(Caching)**일 것입니다. revalidate, force-cache, unstable_cache 등 파편화된 API와 예측하기 어려운 동작 방식은 많은 개발자에게 스트레스를 주었죠.

드디어 Next.js는 이를 해결하기 위해 **‘use cache’**라는 혁신적인 지시어를 도입했습니다. 이제 우리는 페이지나 레이아웃 단위가 아닌, 함수 및 컴포넌트 단위로 캐싱을 아주 직관적으로 제어할 수 있게 되었습니다. 시니어 개발자의 시각으로 이 변화가 왜 중요한지, 그리고 실무에서 어떻게 적용해야 하는지 상세히 살펴보겠습니다.


목차


’use cache’란 무엇인가?

**‘use cache’**는 Next.js가 제안하는 새로운 캐싱 아키텍처의 핵심입니다. 파일 상단이나 특정 함수 내부에 선언하여, 해당 범위 내의 데이터 fetch나 계산 결과를 **Next.js의 데이터 캐시(Data Cache)**에 저장하도록 지시합니다.

과거에는 Fetch API의 옵션을 통해 캐싱을 조절했다면, 이제는 직렬화 가능한(Serializable) 모든 함수 결과물을 캐싱할 수 있습니다. 이는 데이터베이스 쿼리나 복잡한 알고리즘 연산 결과까지도 아주 쉽게 캐싱할 수 있음을 의미합니다.

Diagram showing how 'use cache' works at the function level, interacting with the Global Data Cache and bypassing redundant database or API calls.

왜 기존 방식보다 뛰어난가? (비교 분석)

기존의 unstable_cachefetch 옵션 방식은 다음과 같은 문제점이 있었습니다.

  1. 복잡한 시그니처: unstable_cache는 인자가 복잡하고 관리가 어려웠습니다.
  2. 범위의 제한: Fetch API에 의존적이었기에 DB 쿼리 등을 캐싱하려면 별도의 래핑이 필요했습니다.

반면 **‘use cache’**는 다음과 같은 이점을 제공합니다.

  • 함수 레벨 추상화: 함수 본문 내에 지시어 한 줄만 적으면 끝납니다.
  • 자동 직렬화: 반환값이 JSON으로 변환 가능하다면 무엇이든 캐싱합니다.
  • 유연한 만료 설정: cacheLife, cacheTag 등의 새로운 API와 결합하여 정교하게 제어할 수 있습니다.

실무 코드 예시: 함수 단위 캐싱 구현

가장 일반적인 데이터베이스 조회 로직에 ‘use cache’를 적용해 보겠습니다.

// services/product.ts
import { cacheLife, cacheTag } from 'next/dist/server/use-cache/cache-life';

/**
 * 특정 상품 상세 정보를 가져오는 함수입니다.
 * 'use cache'를 사용하여 함수 결과 전체를 캐싱합니다.
 */
export async function getProductDetails(id: string) {
  "use cache"; // 함수 단위 캐싱 활성화
  
  // 캐시 수명을 'frequently'(짧은 주기)로 설정합니다.
  cacheLife('frequently'); 
  // 특정 태그를 부여하여 나중에 revalidateTag로 만료시킬 수 있게 합니다.
  cacheTag(`product-${id}`);

  console.log(`Fetching product ${id} from DB...`);
  
  const product = await db.product.findUnique({ where: { id } });
  return product;
}

이제 이 함수는 동일한 id로 호출될 때 첫 실행 이후에는 데이터베이스를 조회하지 않고 메모리/파일 시스템 캐시에서 즉시 결과를 반환합니다.


캐시 태그와 Revalidation 전략

캐싱만큼 중요한 것이 **‘언제 캐시를 버릴 것인가’**입니다. ‘use cache’ 환경에서는 revalidateTag와 함께 사용하여 효율적인 업데이트 전략을 세울 수 있습니다.

// app/admin/actions.ts
"use server";

import { revalidateTag } from 'next/cache';

export async function updateProductInfo(id: string, data: any) {
  // 데이터 수정 로직
  await db.product.update({ where: { id }, data });

  // 해당 상품 정보를 사용하는 모든 'use cache' 함수를 만료시킵니다.
  revalidateTag(`product-${id}`);
}

개발자 팁: ‘use cache’ 사용 시 주의사항

💡 시니어의 조언: 직렬화의 함정 ‘use cache’가 적용된 함수는 반환값을 직렬화하여 저장합니다. 따라서 Date 객체, Map, Set 등 JSON으로 바로 변환되지 않는 형식을 반환할 때는 주의가 필요합니다. 가급적 Plain Object를 반환하도록 설계하거나, 클라이언트 컴포넌트로 넘기기 전 포맷팅을 마치는 것이 좋습니다.


FAQ: 자주 묻는 질문

Q1. ‘use cache’는 현재 정식 기능인가요?

‘use cache’는 Next.js 15 버전부터 도입된 실험적(Experimental) 기능입니다. 사용을 위해서는 next.config.js에서 experimental.dynamicIO 옵션을 활성화해야 할 수 있습니다. 프로덕션 도입 전 반드시 최신 릴리스 노트를 확인하세요.

Q2. 클라이언트 컴포넌트에서도 사용할 수 있나요?

아니요. ‘use cache’는 서버 환경(Server Components, Server Actions, Server Functions)에서만 동작합니다. 클라이언트 측 캐싱은 기존과 동일하게 SWR이나 TanStack Query를 사용하는 것이 적절합니다.

Q3. 쿠키나 헤더에 의존적인 데이터도 캐싱되나요?

‘use cache’ 내부에서 cookies()headers()를 호출하면 캐시 키에 해당 정보가 포함됩니다. 하지만 이는 캐시 히트율을 떨어뜨릴 수 있으므로, 필요한 데이터만 인자로 명시적으로 전달받아 캐싱하는 것이 더 권장되는 패턴입니다.


마무리하며

‘use cache’의 도입은 Next.js가 성능 최적화의 복잡성을 얼마나 진지하게 해결하려 하는지 보여줍니다. 이제 우리는 인프라 설정에 매달리는 대신, 비즈니스 로직의 어떤 부분을 캐싱할지 결정하는 데 집중할 수 있게 되었습니다.

더 자세한 아키텍처 설명은 Next.js 공식 문서 - use cache를 참고해 보세요.

이 글이 마음에 드셨나요?

로딩 중...