Next.js

Verified against Claude Code · 2026-07-30

Catch the Date.now()-in-cache trap before Cache Components ships it to production

Audits a route or component for caching correctness under the Cache Components model, flagging any dynamic API call trapped inside a "use cache" boundary.

Claude CodeCursor 2.1Claude (Sonnet 4.6)

The prompt

Ready to copy — highlighted parts are example details you can swap.

<context>
Next.js version and caching mode: Next.js 16, cacheComponents enabled in next.config
Code to audit (routes, layouts, components, and any 'use cache' functions): app/products/[id]/page.tsx plus lib/get-product.ts which is wrapped in 'use cache'
Freshness requirements in plain language — what's allowed to be stale, and for how long: Product descriptions can be up to 10 minutes stale; stock count and 'last updated' timestamp must always be current
</context>

<task>
Audit this code for caching correctness under the Cache Components model, where a component or function is dynamic (re-runs per request) by default UNLESS it's explicitly opted into caching with a 'use cache' directive at the top of the file or function. Do not assume the old "static unless you opt out" model — that default flipped.
</task>

<the_trap_to_check_for>
Inside any function or component marked 'use cache', flag every call to a dynamic API — Date.now(), new Date(), Math.random(), cookies(), headers(), searchParams read directly — as a bug, not a style note. Once that function is cached, whatever value those calls returned on the request that populated the cache entry gets frozen into every cached response until the entry's cacheLife expires or a matching cacheTag is revalidated. A "last updated" timestamp or a per-user value computed inside a 'use cache' scope will silently show the same frozen value to everyone who hits the cache, not just the first requester.
For each instance found, explain: what gets frozen, for how long (based on any cacheLife profile set), and the fix — either move the dynamic read outside the 'use cache' boundary (pass it in as an argument instead), or confirm the staleness is actually intended and the profile is scoped tightly enough.
</the_trap_to_check_for>

<other_checks>
- Does every 'use cache' function have an explicit cacheLife() profile, or is it relying on the default (call this out either way)?
- Are cacheTag() calls specific enough to invalidate correctly (e.g. tagged per-item, not one global tag for an entire dataset) given the freshness requirements above?
- Is anything cached that reads request-specific data (a user ID, an auth token) without that data being part of the cache key — a cross-user cache leak risk?
</other_checks>

<output_format>
A findings table: File/function | Issue | Severity (bug / needs-profile / leak-risk) | Fix. Then a one-paragraph summary of whether the current caching setup matches the stated freshness requirements.
</output_format>
Customize the highlighted detailsoptional — the prompt above already works

Why this works

This targets one specific, well-documented failure mode instead of asking generically 'review my caching' — a prompt vague enough to let the model nod along without checking anything concrete. The Cache Components model inverts the old default (static unless opted out, via export const dynamic) to dynamic unless opted in (via 'use cache'), and that inversion is exactly where the Date.now() trap lives: code written under the old mental model assumed a timestamp read at request time would just work, but once that same function gets wrapped in 'use cache' for a performance win, the timestamp freezes into the cache entry. Naming the exact dynamic APIs to search for (Date.now, new Date, Math.random, cookies, headers) gives the model a grep-able checklist instead of a vibe, and the cross-user cache-leak check catches the second most common Cache Components mistake: caching something keyed by a user without that user being part of the cache key.

What you get back

lib/get-dashboard-summary.ts, marked 'use cache': calls new Date() to compute 'as of {date}' — frozen into the cache entry. With no cacheLife() set, this defaults to Next.js's default profile, so every dashboard visitor sees the same 'as of' date until the entry ages out or is revalidated, not the actual current time. Fix: pass the current date in as an argument from the (uncached) caller, or drop the caching if this value must always be live.

Verified against

Claude Code Sonnet 4.6 · 2026-07-30

Cursor 2.1 · 2026-08-02

Changelog

  • 2026-07-30 Initial version, written against Next.js 16's stable Cache Components model and tested with Claude Code.
  • 2026-08-02 Added the cross-user cache-leak check after a review missed a cache key that omitted the session user ID.

Need this built into your business?

If a prompt isn't enough — custom software, built and maintained for you — that's Scult's day job.

EXPLORE CUSTOM SOFTWARE
All Next.js prompts

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY