Next.js 캐싱의 혁명: 'use cache' 지시어 완벽 가이드 및 실무 활용법
Next.js 개발자들을 가장 괴롭혔던 주제를 하나 꼽으라면 단연 **캐싱(Caching)**일 것입니다. revalidate, force-cache, unstable_cache 등 파편화된 API와 예측하기 어려운 동작 방식은 많은 개발자에게 스트레스를 주었죠.
드디어 Next.js는 이를 해결하기 위해 **‘use cache’**라는 혁신적인 지시어를 도입했습니다. 이제 우리는 페이지나 레이아웃 단위가 아닌, 함수 및 컴포넌트 단위로 캐싱을 아주 직관적으로 제어할 수 있게 되었습니다. 시니어 개발자의 시각으로 이 변화가 왜 중요한지, 그리고 실무에서 어떻게 적용해야 하는지 상세히 살펴보겠습니다.
목차
- ‘use cache’란 무엇인가?
- 왜 기존 방식보다 뛰어난가? (비교 분석)
- 실무 코드 예시: 함수 단위 캐싱 구현
- 캐시 태그와 Revalidation 전략
- 개발자 팁: ‘use cache’ 사용 시 주의사항
- FAQ: 자주 묻는 질문
’use cache’란 무엇인가?
**‘use cache’**는 Next.js가 제안하는 새로운 캐싱 아키텍처의 핵심입니다. 파일 상단이나 특정 함수 내부에 선언하여, 해당 범위 내의 데이터 fetch나 계산 결과를 **Next.js의 데이터 캐시(Data Cache)**에 저장하도록 지시합니다.
과거에는 Fetch API의 옵션을 통해 캐싱을 조절했다면, 이제는 직렬화 가능한(Serializable) 모든 함수 결과물을 캐싱할 수 있습니다. 이는 데이터베이스 쿼리나 복잡한 알고리즘 연산 결과까지도 아주 쉽게 캐싱할 수 있음을 의미합니다.
왜 기존 방식보다 뛰어난가? (비교 분석)
기존의 unstable_cache나 fetch 옵션 방식은 다음과 같은 문제점이 있었습니다.
- 복잡한 시그니처:
unstable_cache는 인자가 복잡하고 관리가 어려웠습니다. - 범위의 제한: 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를 참고해 보세요.