Repository navigation
Static page with use cache still shows a Suspense fallback on Vercel #99863
Replies: 6 comments 7 replies
|
I think the important distinction here is between the cached data and the rendering boundary created by
However, So I wouldn't interpret the behavior as:
I'd interpret it more as:
In your example, the fact that If the books are genuinely part of the static shell and you don't need a loading state for them, I would simply remove the export default function Page() {
return (
<main>
<h1>Books</h1>
<LatestBooks />
</main>
)
}That matches the Cache Components model: use One thing I'd also verify in a minimal reproduction is whether you're testing with a hard refresh versus client-side navigation. Those exercise different cache/rendering paths, so reproducing the flash consistently in both cases would help determine whether this is expected Suspense behavior or an actual Next.js/Vercel rendering issue. |
|
I tried a minimal production reproduction of your two routes on Next.js 16.2.10 / React 19.2.1, with <main><h1>With Suspense</h1><!--$--><ul><li>BUILD_TIME_BOOK_SENTINEL</li></ul><!--/$--></main>I also checked three document requests per route through So I would not conclude that wrapping already-prerendered content in Suspense necessarily makes it stream separately on every hard refresh. For your three follow-up questions:
To distinguish a rendering issue from cache/revalidation behavior, I would compare:
A cached function's runtime cache and the prerendered route output are different things; serverless runtime cache persistence alone does not prove the route should display a fallback. See Next.js caching/prerendering, Could you share the exact Next.js version and a minimal reproduction/deployment? Your snippet alone did not produce the flash in the setup above, so removing Suspense may hide the symptom without explaining the deployment difference. |
|
Building on the two replies above — the key thing to check is whether the fallback is baked into the prerendered shell that is actually being served, because that is what determines whether you see a flash. With
That "sometimes" you observed usually means one build had the boundary complete and another didn't (or a revalidation/ISR pass regenerated the shell with a slow DB query). You can verify which case you're in without guessing: # Is the fallback in the HTML Vercel is actually serving?
curl -s https://your-app/with-suspense | grep -o "Loading books..."
# Is the fallback in the prerendered artifact from your build?
grep -o "Loading books..." .next/server/app/with-suspense.htmlIf "Loading books..." appears in the served HTML (not just briefly during hydration), the problem is in the prerendered shell, not the client or the data cache. Fixes then are on the build side: make the cached query fast/deterministic at build time, or drop the |
|
Hi, this is likely being caused by outlining. HTML still has to be downloaded. If you refer to https://react.dev/reference/react/Suspense#what-activates-a-suspense-boundary, you'll see that:
Even if your server/CDN has a fully rendered page, when it is requested, the page does not necessarily travel as a whole unit, in one packet to your browser so to speak. It arrives in chunks, and browsers can begin painting the page as those arrive. With outlining, React places the fallback where the boundary is, and sends the boundary's content later in the same HTML document, in a hidden element. The browser paints that fallback UI, and when the content arrives, a small inline script swaps the fallback with the content.
You can confirm if outlining is happening by looking at the HTML of the page, like the view-source bit. You'd see something like To your questions:
Note that, as far as React is concerned, if you wrap content in Suspense, then it is OK to show the fallback. Be it by outlining, slow network, CPU bound conditions etc. This is not Cache Components related necessarily, it is Suspense that gives you this capability. |
|
@icyJoseph's streaming point is the other half of the picture — and the curl check above distinguishes which of the two you are hitting: Case A — the fallback is in the raw HTML ( Case B — the content is in the raw HTML, and you still see a flash: that is the streaming reveal icyJoseph describes — a boundary with enough content activates during download, so the browser paints the fallback until the boundary's chunk arrives. The fix is structural: make the boundary smaller (move For the original repro — both pages prerendered, same cached data, only |
|
@mshearer0 circling back — did the raw-HTML check (curl the deployed URL and grep for the fallback text) tell you which of the two cases you were in? If the fallback is in the served HTML it is the prerender-side fix, otherwise it is the streaming/boundary-size one. Happy to dig further with your build output if you can share what the check showed. |
Uh oh!
There was an error while loading. Please reload this page.
Summary
I have two static pages, both deployed to Vercel. Both load the same books from a function marked with
"use cache"andcacheLife("max"). Neither page reads cookies, headers, or search params. Both are prerendered.On Vercel, a refresh of
/with-suspensesometimes shows "Loading books...", then the books. A refresh of/without-suspenseshows the books immediately, with no flash.Why does the Suspense fallback show on a static page when the data is already cached? Should cached content on a static page be rendered without
<Suspense>?I am struggling to understand the difference and proper usage.
Additional information
No response
Example
No response
All reactions