본문 바로가기
Q&A2026년 5월 2일1분 읽기

Next.js 16에서 'use cache' 썼더니 cookies() 에러가 떠요

류
류지훈
조회 735댓글 3

Next.js 15 → 16으로 올리면서 메인 페이지에 'use cache'를 추가했는데, 빌드할 때 다음 에러가 나옵니다.

Error: Detected dynamic API 'cookies()' inside cached scope.
  at app/page.tsx:12

실제로 페이지 안에서 사용자 세션을 cookies로 읽고 있긴 한데요, 이걸 어떻게 풀어야 하나요? cache를 포기해야 하는 건가요?

댓글 3

김김영삼6개월 전

16에서 의도된 동작입니다. 캐시된 스코프 안에서는 요청별로 다른 값(cookies, headers, searchParams)을 읽을 수 없어요 — 캐시 키가 무한히 갈라지니까요. 두 가지 패턴 중 하나로 풀면 됩니다.

패턴 A — 동적 부분을 자식 컴포넌트로 분리
// app/page.tsx — 정적인 부분만 캐시
'use cache'
import UserGreeting from './UserGreeting'
import ProductList from './ProductList' // 캐시됨

export default async function Home() {
const products = await getProducts() // 캐시
return (
<>
<UserGreeting /> {/* 동적 — 별도 컴포넌트 */}
<ProductList products={products} />
</>
)
}

// app/UserGreeting.tsx — 동적 (캐시 안 함)
import { cookies } from 'next/headers'
export default async function UserGreeting() {
const session = (await cookies()).get('session')
return <p>안녕하세요, {session?.value}님</p>
}
패턴 B — 캐시 자체를 포기하고 fetch만 캐시
// page.tsx ('use cache' 없음)
import { cookies } from 'next/headers'
import { unstable_cache } from 'next/cache'

const getProducts = unstable_cache(
async () => db.product.findMany(),
['products'],
{ revalidate: 60 }
)

export default async function Home() {
const session = (await cookies()).get('session')
const products = await getProducts() // 함수 단위 캐시
return ...
}

대부분의 경우 패턴 A가 더 빠르고 더 자연스럽습니다. 페이지의 95%가 캐시되고, 사용자 인사 한 줄만 동적이니까요. 컴포넌트를 잘 쪼갠다는 건 React Server Components의 핵심이기도 합니다.

류류지훈(guest)6개월 전
그런데 UserGreeting 컴포넌트가 페이지 한가운데 있어서 분리하면 레이아웃이 깨지는데요, 이런 경우는요?
김김영삼6개월 전

레이아웃은 그대로 두고 컴포넌트만 분리하면 됩니다. 부모 컴포넌트가 캐시되더라도 자식 컴포넌트의 동적 부분은 그 위치에 그대로 렌더링됩니다 — RSC의 partial prerendering이 정확히 이걸 해결하려고 만들어진 기능이에요.

// 시각적 레이아웃은 동일
'use cache'
export default async function Page() {
const data = await getData()
return (
<div className="grid">
<Sidebar items={data.menu} /> {/* 캐시 */}
<main>
<HeroSection /> {/* 캐시 */}
<Suspense fallback={<Skeleton />}>
<UserGreeting /> {/* 동적 */}
</Suspense>
<ProductGrid products={data.products} /> {/* 캐시 */}
</main>
</div>
)
}

Suspense로 감싸면 정적 부분이 먼저 스트리밍되고 동적 부분은 데이터 준비되는 대로 끼어듭니다. 사용자에겐 페이지 전체가 더 빨라 보입니다.

Ctrl+Enter로 등록