핵심 요약
React cache()는 "요청 단위 memoization"이지 데이터 캐시가 아니다. 1년 운영으로 정착한 6가지 패턴 — fetch 중복 제거, 권한 체크 단일화, ORM 호출 최소화, 직렬화 우회, 인자 정규화, layout-page 분리. 잘못 쓰면 메모리 누수와 동등성 미스로 캐시가 항상 미스.
1. cache() ≠ 데이터 캐시
cache()는 같은 요청 안에서만 동일 인자 호출을 memoize. 요청이 끝나면 사라진다. CDN 캐시, 디스크 캐시 아님. 데이터 캐시는 unstable_cache 또는 'use cache' 디렉티브.
2. 패턴 1 — 권한 체크 단일화
import { cache } from 'react'
import { cookies } from 'next/headers'
export const getUser = cache(async () => {
const token = (await cookies()).get('session')?.value
if (!token) return null
return verifyJWT(token)
})
layout, page, 5개 component에서 getUser() 호출해도 JWT 검증은 1번. DB hit 5→1.
3. 패턴 2 — 동등성 키 정규화
인자는 참조 동등성(===). 객체 매번 새로 만들면 캐시 매번 미스. 객체 인자는 JSON.stringify로 키 변환 또는 primitive로 분해.
// ❌ 매번 새 객체 — 캐시 미스
getPost(cache({id, locale}))
// ✅ primitive
const _getPost = cache(async (id: number, locale: string) => {...})
4. 패턴 3 — 직렬화 함정
Server Component 사이로 props 넘기면 직렬화. Date, Map, Set은 통과 못하니 ISO 문자열·배열로 변환. cache()로 감싼 함수가 Map을 반환하면 client component에서 받을 때 에러.
5. 패턴 4 — layout-page 분리 호출
layout과 page에서 같은 getUser() 호출 시 layout이 먼저 await되면 page가 캐시 hit. Promise.all로 병렬 awaiting하면 두 번 다 hit. preload 패턴이 강력.
6. 함정 정리
- 'use server' 액션 안에서 cache() — 액션 호출마다 새 요청이라 무의미
- cache(fn)을 매번 새로 만들기 — 인스턴스가 다르면 캐시 따로
- 에러 캐시 — throw도 memoize. 에러 응답을 캐싱하기 싫으면 try-catch로 일반 반환
- Client Component에서 cache() 쓰기 — no-op, 경고도 안 나옴

댓글 0