Skip to content

use cache + cacheLife unexpectedly requires Suspense boundary #74158

Open
@jonathanhefner

Description

@jonathanhefner

Link to the code that reproduces this issue

https://codesandbox.io/p/devbox/dawn-voice-vrxzm9

To Reproduce

  1. Visit the reproduction app
  2. Click the cacheLife("minutes") link. Notice that it works and does not cause errors.
  3. Go back, and click the cacheLife("seconds") link. Notice the following server-side error:

    [ Server ] Error: Route "/seconds": A component accessed data, headers, params, searchParams, or a short-lived cache without a Suspense boundary nor a "use cache" above it. We don't have the exact line number added to error messages yet but you can see which component in the stack below. See more info: https://nextjs.org/docs/messages/next-prerender-missing-suspense

  4. Go back, and click the cacheLife({ expire: 299 }) link. Notice the following server-side error:

    [ Server ] Error: Route "/expire299": A component accessed data, headers, params, searchParams, or a short-lived cache without a Suspense boundary nor a "use cache" above it. We don't have the exact line number added to error messages yet but you can see which component in the stack below. See more info: https://nextjs.org/docs/messages/next-prerender-missing-suspense

Current vs. Expected behavior

The current behavior demands a Suspense boundary when the developer changes a cacheLife("minutes") call to cacheLife("seconds") call. I expect for such a change to be local / isolated, and to not require any other changes to an app.

Likewise for changing a cacheLife({ expire: 300 }) call to cacheLife({ expire: 299 }) (or, generally, from >= 300 to < 300).

Provide environment information

Operating System:
  Platform: linux
  Arch: x64
  Version: #1 SMP PREEMPT_DYNAMIC Sun Aug  6 20:05:33 UTC 2023
  Available memory (MB): 4102
  Available CPU cores: 2
Binaries:
  Node: 20.9.0
  npm: 9.8.1
  Yarn: 1.22.19
  pnpm: 8.10.2
Relevant Packages:
  next: 15.1.1-canary.13 // Latest available version is detected (15.1.1-canary.13).
  eslint-config-next: N/A
  react: 19.0.0
  react-dom: 19.0.0
  typescript: 5.3.3
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

dynamicIO

Which stage(s) are affected? (Select all that apply)

next dev (local), next start (local), Vercel (Deployed), Other (Deployed)

Additional context

The cause of this behavior is this code:

(entry.revalidate === 0 || entry.expire < DYNAMIC_EXPIRE)
) {
// In a Dynamic I/O prerender, if the cache entry has revalidate: 0 or if the
// expire time is under 5 minutes, then we consider this cache entry dynamic
// as it's not worth generating static pages for such data. It's better to leave
// a PPR hole that can be filled in dynamically with a potentially cached entry.
if (cacheSignal) {
cacheSignal.endRead()
}
return makeHangingPromise(
workUnitStore.renderSignal,
'dynamic "use cache"'
)

I discussed this code a bit in #72145 (comment). I understand the intention behind the code, however, it results in poor DX. It's as if I, the app developer, have entered into an agreement with Next.js, wherein I add 'use cache' + revalidation as appropriate, and in exchange I get improved performance. But then Next.js suddenly decides to do extra work that I didn't agree to, and demands more payment in return (a Suspense boundary).

To be clear: I do not think the solution is documentation. Even if this behavior were documented, it would be poor DX.

Metadata

Metadata

Assignees

No one assigned

    Labels

    dynamicIORelated to dynamicIO.linear: nextConfirmed issue that is tracked by the Next.js team.

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions