본문으로 건너뛰기
개발 머꼬
개발 노트Next.js
hohyeon.dev28

캐시를 명시적으로 켜는 use cache와 그 대가

  • #Engineering Note
  • #Next.js
  • #Performance

문제 발생

목록 페이지가 매 요청마다 같은 쿼리를 반복했습니다. 캐시를 켜려고 use cache를 붙였더니 빌드가 이런 오류로 멈췄습니다.

Error: Route used "cookies" inside "use cache".

세션을 읽어 개인화된 부분이 그 안에 함께 있었기 때문입니다.

원인 분석

use cache는 순수 함수를 전제로 합니다. Next.js 문서는 이 디렉티브를 라우트·React 컴포넌트·함수를 캐시 가능하다고 표시하는 것으로 설명하고, 사용하려면 next.config.tscacheComponents를 켜야 하며 캐시되는 함수와 컴포넌트는 async여야 한다고 적습니다.

캐시 키가 무엇인지도 명시돼 있습니다 — 빌드 ID, 함수 ID, 그리고 직렬화 가능한 인자입니다. 여기에 중요한 성질이 하나 더 있습니다 — 캐시된 함수가 바깥 스코프의 변수를 참조하면 그 변수들이 자동으로 캡처되어 인자로 바인딩되고, 캐시 키의 일부가 됩니다.

그래서 요청별 데이터는 금지됩니다. 문서가 분명히 합니다 — 캐시된 함수와 컴포넌트는 cookies(), headers(), searchParams 같은 런타임 API에 접근할 수 없으며, 그 제약은 호출 스택을 따라갑니다. 캐시된 함수가 부르는 헬퍼가 그걸 읽어도 같은 오류가 납니다.

해결 방안

  1. 요청 데이터는 캐시 밖에서 읽어 인자로 넘깁니다. 문서가 권장 패턴으로 제시하는 방식입니다.
export default async function Page() {
  const theme = (await cookies()).get("theme")?.value ?? "light";
  return <Feed theme={theme} />;      // Feed 안에서 'use cache'
}
  1. 수명을 호출 지점에 명시합니다. cacheLife를 생략하면 default 프로필이 적용되고 수명이 코드에서 보이지 않습니다.
async function getPosts() {
  "use cache";
  cacheLife("hours");
  return db.posts.findMany();
}
  1. 무효화가 필요하면 태그를 답니다. cacheTag로 표시하고 뮤테이션에서 updateTag/revalidateTag로 무효화합니다.

  2. 직렬화 제약을 확인합니다. 문서가 나열하듯 클래스 인스턴스, 함수(패스스루 제외), Symbol, URL 인스턴스는 인자로 쓸 수 없습니다.

  3. 자체 호스팅과 서버리스의 차이를 감안합니다. 문서 표 그대로 서버리스에서는 인메모리 캐시 항목이 요청 간에 유지되지 않는 것이 보통이고, 자체 호스팅에서는 유지됩니다. 이 저장소처럼 Docker로 직접 띄우는 구성은 후자입니다.

  4. 캐시 전략은 한 번에 정합니다. 이 저장소는 sitemap을 force-dynamic으로 되돌린 이력이 있습니다 — 캐시를 다시 켤 때는 그 결정과 함께 재검토합니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.