Quiz - The Cache Components rendering model
You’ve finished an org-scoped invoices dashboard. It reads cookies() for the session and queries the database per request, and it has no 'use cache' anywhere. A teammate flags it in review: “this whole page is dynamic, that’s a performance smell — cache it.” Under Cache Components, what’s the right response?
'use cache' so it prerenders, then read the cookies outside the cached scope to keep it correct.'use cache' boundary to opt into the Next.js 16 model.'use cache' can’t work: a cached scope is forbidden from reading cookies() at all — it’s a build error, not something you route around. And a route without 'use cache' is already running the Next.js 16 model fully dynamically; nothing falls back to the old implicit-trigger flowchart.A dashboard route is already dynamic (it renders per request). One widget deep inside it runs a slow-ish query. A reviewer suggests wrapping just that widget in 'use cache' to “speed up the page.” What’s the trade-off they’re missing?
'use cache' widget inside a dynamic parent is a build error, so the suggestion can’t ship at all.A cached fetcher reaches a value from its enclosing scope rather than taking it as an argument:
export async function Dashboard({ userId }: { userId: string }) { async function getData(filter: string) { 'use cache'; return db.query.events.findMany({ where: and(eq(events.userId, userId), eq(events.kind, filter)), }); } return <Events rows={await getData('errors')} />;}Which statements about this code are correct? Select all that apply.
userId is captured from the closure and folded into the cache key alongside filter, so two users with the same filter get two distinct entries.'use cache' forbids capturing outer variables, so this fails to compile until userId is passed in as an argument.getData instead captured Date.now() from the outer scope, that timestamp would be frozen into the entry and reused on later reads rather than re-evaluated per request.userId correctly partitions entries per user. That same capture is why non-determinism is a sharp edge — Date.now() runs once when the entry is computed and then stays frozen, which is the model working as designed, not a bug. The forbidden capture is request-time APIs like cookies(), not ordinary serializable values.You’re deciding the lifetime for a cached product catalog that admins edit a few times a day through your own admin panel. The reflex “cache it as long as possible, cacheLife('max'), done” is half-right. What’s the senior shape, and why?
cacheLife('max') paired with a tag — max stops the pointless clock-based refresh, and the tag lets the admin’s save push an invalidation the instant the data changes. max with no tag is a footgun: nothing refreshes it on demand.cacheLife('max') alone is correct — max means the entry never goes stale, so an explicit invalidation is redundant.cacheLife('seconds') — a short timer is the only way to reflect admin edits quickly, since tags can’t invalidate a max-lifetime entry.max removes the meaningless background timer and a tag carries the push — the admin’s save invalidates exactly that entry. max alone is the footgun the lesson calls out: no clock, no tag, so it only refreshes on a slow monthly timer or a deploy. A tag-targeted invalidation overrides cacheLife regardless of profile, so the “tags can’t touch a max entry” claim is backwards, and dynamic rendering throws away reuse you could have had for free.You have two functions in your data layer: getCurrentUser() (reads the session via headers(), called from the layout, the page, and a deep nav in one render) and getProductCatalog() (a request-independent fetch every user should share). Which caching primitive goes on each?
cache() (from react) on getCurrentUser and 'use cache' on getProductCatalog — the hinge is whether the work reads request data.'use cache' on both — it deduplicates within a render anyway, so it covers the per-render case too.cache() on both — a single primitive for all deduplication keeps the data layer consistent.getCurrentUser reads headers(), so it can’t be 'use cache' — that’s a build error — and cache() is the per-request memoizer that dedupes those three calls into one read. getProductCatalog is request-independent, so 'use cache' persists it across requests and users; using cache() there would re-run the work every request and waste the cross-request reuse. They’re different layers, not interchangeable tools.A user edits invoice #42 in a form and your Server Action redirects them straight to the invoice’s detail page. The list and detail are cached. To guarantee they see their own edit on that next screen, which invalidation tool belongs at the revalidate seam?
updateTag — it expires the tagged entries so the redirect’s render blocks for fresh data, delivering read-your-writes.revalidateTag(tag, 'max') — stale-while-revalidate is the safe default for every mutation, including form submits.router.refresh() after the redirect — it re-pulls the route’s Server Components, which clears the stale cache entry.updateTag expires the tag so the redirect target blocks for fresh data. revalidateTag(..., 'max') is stale-while-revalidate — the user could land on the redirect before the background refresh and see their old data, which is the exact bug. And router.refresh() re-renders Server Components but does not bust the 'use cache' store, so over a cached read it’s a no-op.You inherit a route that starts with export const revalidate = 3600, and the Next.js 16 build rejects it. What’s the correct migration?
'use cache' and set cacheLife('hours') — the preset closest to the interval, matched to the data’s shape rather than a hand-rolled second count.await connection() at the top of the page so the route opts back into timed revalidation.cacheLife({ revalidate: 3600 }) at module scope, keeping the exact one-hour interval.revalidate export is gone, and its value translates into a cached scope: 'use cache' plus the cacheLife preset closest to the interval, picked by the data’s shape. connection() is the opposite of timed caching — it’s the explicit dynamic marker for request-time work with no other signal. And cacheLife is called inside the cached function body, never at module scope, where it throws; the convention also favors a named preset over a raw { revalidate: 3600 } object.Quiz complete
Score by topic