Keep cookies() out of cache
Read cookies() in the uncached parent and pass the value in.
Next.js docsAPI reference · unstable_cache
unstable_cache (legacy): replaced by the use cache directive in Next.js 16.
From next-cache-components, “Runtime Data Constraint”.
Pass the session in
Pick a session, request twice, then switch the directive.
cookies() insideBadasync function CachedProfile() {
'use cache'
const s = (await cookies()).get('session')
return <div>{(await fetchUserData(s)).name}</div>
}
Press Request.
Passed in from parentGood
build
…
fn…
args…
…
Press Request.
Cache entries0
<CachedProfile sessionId={session} />import { cookies } from 'next/headers'
export default async function ProfilePage() {
const session = (await cookies()).get('session')?.value
return <CachedProfile sessionId={session} />
}
async function CachedProfile({ sessionId }: { sessionId?: string }) {
'use cache'
const user = await fetchUserData(sessionId)
return <div>{user.name}</div>
}
Closures are part of the key
Render as Ana, switch to Ben, render again.
unstable_cacheBadconst getData = unstable_cache(
async (filter) => db.users.find(userId, filter),
['user-data'],
{ tags: ['users'], revalidate: 60 }
)
keyParts
['user-data']
args…
userId…
…
Press Render.
Cache entries0
key = keyParts + args'use cache'Goodconst getData = async (filter) => {
'use cache'
return db.users.find(userId, filter)
}
build
…
fn…
closure…
args…
…
Press Render.
Cache entries0
key = userId + filterimport { cacheLife, cacheTag } from 'next/cache'
async function Component({ userId }: { userId: string }) {
const getData = async (filter: string) => {
'use cache'
cacheTag('users')
cacheLife({ revalidate: 60 })
return db.users.find(userId, filter)
}
return <List items={await getData('active')} />
}
Random values run once
Request a few times, then Deploy in the toolbar.
Inside 'use cache'Bad
async function RequestId() {
'use cache'
return { id: crypto.randomUUID(), at: Date.now() }
}
Responses0 requests
build …After
connection()Goodasync function RequestId() {
await connection()
return { id: crypto.randomUUID(), at: Date.now() }
}
Responses0 requests
<Suspense>import { connection } from 'next/server'
async function RequestId() {
await connection()
const id = crypto.randomUUID()
return <span>{id}</span>
}
Why it matters, pros and cons
Why it matters
The key sees only arguments and closures, never a cookie read inside.
Pros
- Every input shows up in the key
- No
keyPartsto forget - Cached functions stay pure and testable
Cons
- Each distinct argument adds a cache entry
- Arguments must be serializable
- Data nobody requests twice gains nothing from caching