{"record":{"id":"3905a487553b8e7f","repo":"vercel/next.js","slug":"route-route-unexpected-cache-miss-after-cach","errorCode":null,"errorMessage":"Route \"${route}\": Unexpected cache miss after cache warming phase during prerendering. This is likely caused by non-deterministic arguments that differ between the cache warming phase and the final prerender phase (e.g. unstable array order). Ensure that arguments passed to cached functions are deterministic.","messagePattern":"Route \"(.+?)\": Unexpected cache miss after cache warming phase during prerendering\\. This is likely caused by non-deterministic arguments that differ between the cache warming phase and the final prerender phase \\(e\\.g\\. unstable array order\\)\\. Ensure that arguments passed to cached functions are deterministic\\.","errorType":"console","errorClass":"UnexpectedCacheMissError","httpStatus":null,"severity":"warning","filePath":"packages/next/src/server/use-cache/use-cache-wrapper.ts","lineNumber":2731,"sourceCode":"            return makeRuntimeHangingPromise(\n              workUnitStore.renderSignal,\n              workStore.route,\n              'dynamic \"use cache\"',\n              workUnitStore\n            )\n          }\n        // fallthrough\n        case 'prerender-runtime':\n          if (!cacheSignal) {\n            // This is the final prerender (cacheSignal is null), which means\n            // all caches should have been warmed during the prospective\n            // prerender. A cache miss here indicates that the cache key is\n            // non-deterministic (e.g. due to unstable array order in the\n            // arguments). Known dynamic keys (e.g. from fallback params) are\n            // already handled by the early return above. We return a hanging\n            // promise so this becomes a dynamic hole rather than generating a\n            // broken cache entry that gets aborted.\n            console.warn(new UnexpectedCacheMissError(workStore.route))\n            // This is an anomaly (non-deterministic cache key), so we can't\n            // know whether a runtime prerender would resolve it. Treat it as\n            // runtime data, conservatively: the cost is at most a redundant\n            // runtime prefetch request.\n            return makeRuntimeHangingPromise(\n              workUnitStore.renderSignal,\n              workStore.route,\n              'dynamic \"use cache\"',\n              workUnitStore\n            )\n          }\n          break\n        case 'prerender-legacy':\n        case 'request':\n        case 'cache':\n        case 'private-cache':\n        case 'unstable-cache':\n        case 'generate-static-params':","sourceCodeStart":2713,"sourceCodeEnd":2749,"githubUrl":"https://github.com/vercel/next.js/blob/4fed8eaf197aaa60fd85371352482199a4c2107f/packages/next/src/server/use-cache/use-cache-wrapper.ts#L2713-L2749","documentation":"During static prerendering with Cache Components / use cache, Next.js first runs a cache-warming phase and then the final prerender phase. If a 'use cache' function hits a cache miss in the final phase, its cache key was non-deterministic across the two phases (for example an argument array with unstable ordering). Because the result cannot be trusted, Next.js warns and returns a hanging promise so the route becomes a dynamic hole instead of generating a broken cache entry.","triggerScenarios":"Calling a 'use cache' function during prerender with arguments that are not stable between the warming phase and final prerender: arrays whose element order differs per render (e.g. derived from Set iteration, object key iteration, unsorted query results), or other non-deterministic values (Date.now-derived data, random values) passed as cache arguments.","commonSituations":"Passing an array of IDs collected from an unsorted database/GraphQL response; spreading a Set or Object.keys() into arguments without sorting; mapping fallback params into cache calls in differing order; upgrading to cache components and previously working pages now warning during build.","solutions":["Make cached-function arguments deterministic: sort arrays (e.g. [...ids].sort()) before passing them to 'use cache' functions.","Avoid passing whole non-deterministic objects/arrays; pass a stable scalar key (joined sorted string) and reconstruct inside the function.","Ensure any data used to build arguments is fetched from a cached function itself so it is identical across phases.","Remove non-deterministic values (Date.now(), Math.random(), request-time data) from cache arguments; derive them inside the cached function instead."],"exampleFix":"// before\nexport async function getProducts(ids: string[]) {\n  'use cache'\n  return db.products.findMany({ where: { id: { in: ids } } })\n}\n// page\nconst ids = new Set(rawIds)\nconst products = await getProducts([...ids])\n\n// after\nexport async function getProducts(ids: string[]) {\n  'use cache'\n  const sorted = [...ids].sort()\n  return db.products.findMany({ where: { id: { in: sorted } } })\n}\n// page\nconst products = await getProducts([...new Set(rawIds)].sort())","handlingStrategy":"validation","validationCode":"// Run during development before prerendering / before calling a cached fn\nfunction assertDeterministicArgs(args: unknown[]): void {\n  const seen = new Set<string>()\n  for (const arg of args) {\n    if (typeof arg === 'object' && arg !== null) {\n      const key = JSON.stringify(arg, Object.keys(arg as object).sort())\n      if (seen.has(key)) {\n        throw new Error(\n          `Non-deterministic cache argument ordering detected: ${key}. Sort arrays/objects before passing to 'use cache' functions.`\n        )\n      }\n      seen.add(key)\n    } else if (typeof arg === 'function') {\n      throw new Error('Functions are not deterministic cache arguments.')\n    }\n  }\n}","typeGuard":"function isDeterministicCacheArg(arg: unknown): boolean {\n  return (\n    typeof arg === 'string' ||\n    typeof arg === 'number' ||\n    typeof arg === 'boolean' ||\n    (Array.isArray(arg) && arg.every(isDeterministicCacheArg))\n  )\n}","tryCatchPattern":"// The warning itself is not throw-able in your page (it becomes a dynamic hole),\n// but guard runtime fallback for dynamic holes:\nimport { Suspense } from 'react'\n\ntry {\n  // During prerender a hanging promise is returned; at runtime it resolves.\n  const data = await getProducts(sortedIds)\n} catch (e) {\n  if (e instanceof Error && e.name === 'DynamicServerError') {\n    // Route was demoted to dynamic due to non-deterministic cache key.\n    // Fix the key (sort inputs) rather than suppressing here.\n  }\n  throw e\n}\n// Or wrap in Suspense so a dynamic hole renders gracefully:\n// <Suspense fallback={<Skeleton/>}><Products ids={sortedIds}/></Suspense>","preventionTips":["Always sort arrays before passing them as arguments to 'use cache' functions.","Convert Sets and Maps to sorted arrays/entries before using them as cache keys.","Do not use Date.now(), Math.random(), or request-scoped values in cache arguments.","Fetch input data for cache arguments through other cached functions so values are identical across warming and prerender phases.","Run builds locally (next build) with cache components enabled to catch the warning before CI."],"tags":["use-cache","prerender","cache-key","non-determinism","build-time"],"backgroundTag":"invalid-argument-value","analyzedSha":"4fed8eaf197aaa60fd85371352482199a4c2107f","analyzedAt":"2026-09-06T13:38:56.687Z","contentChangedAt":"2026-09-06T13:38:56.687Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}