1import { cacheLife, cacheTag } from 'next/cache'23async function getData(id: string) {4 'use cache'5 cacheLife('hours')6 cacheTag('items', `item-${id}`)7 return db.items.findUnique({ where: { id } })8}
Lifetime with cacheLife
Profiles: minutes, hours, days, weeks, max.
Or exact seconds: { stale, revalidate, expire }.
No request data means the cache fills at build time.
Invalidation
updateTag(tag) — immediate, read-your-writes.
revalidateTag(tag, 'max') — background refresh.
refresh() — refresh uncached data only.
Runtime data rule
You cannot read cookies(), headers(), or searchParams inside 'use cache'. Read the value outside, then pass it in as an argument. The exception is 'use cache: private' for per user data.
Instant navigation rules
Push each Suspense boundary down to the data it guards.
Hoist any element that appears in both the fallback and the result.
Keep the main heading out of every boundary.
A top-level await blocks the whole subtree.
Page load vs client nav
Page load: whole tree renders. Ships the static shell as HTML.
Client nav: only parts below the shared layout re-render.
A boundary must sit below the lowest shared layout to cover both.
useSearchParams() suspends on load, resolves on client nav.
Prefetch choice
Default <Link> warms the shared App Shell.
prefetch={true} resolves URL data before the click.
Link = intent. The prefetch export = cost ceiling.
For dense lists, prefetch on hover, not on view.
prefetch segment config
'auto' — the default. Do not write it out.
'partial' — adopt one route without the global flag.
'force-disabled' — never prefetch this or deeper segments.
instant = false — the route is dynamic by design.
Session data in the shell
A route reading cookies() gets session data in its shell.
Extract and pass: read the cookie outside, pass it into 'use cache'. Shared entry.
'use cache: private': reads runtime data inside, cached in the browser only.
Private cache needs stale of at least 5 minutes to ride the shell.
Validate and test
Dev overlay validates every page. Fix cards: Stream, Cache, Block.
Navigation Inspector freezes the shell so you can see it.
instant() from @next/playwright scopes assertions to instant UI.