[Bug Demo] Static generation runs on every request for routes with `unstable_instant = false` at the root
This is a demonstration of a bug, not a fix. The debug logs added here
are intentional and meant to illustrate the issue.
When a route has `unstable_instant = false` at the root segment (i.e.
`connection()` called outside a Suspense boundary), the route is marked
as Dynamic in the build output. At runtime with Cache Components enabled,
`prerenderToStream` and `collectSegmentData` run on every single request
instead of caching the static generation result.
In contrast, when the same `connection()` call is wrapped in a Suspense
boundary — either with a real fallback (partial prerender) or with a null
fallback (the old "suspense around body" pattern) — neither function runs
at runtime. The static generation result is served from cache as expected.
I haven't thought of a way to write a regression test for this that
doesn't rely on implementation details. Logging inside a component would
get de-duped by the memory cache anyway. I've demonstrated by adding logs
to Next.js itself for now.
Steps to reproduce:
cd test/e2e/app-dir/segment-cache/bug-dynamic-prerender-per-request
pnpm next build
pnpm next start
# In another terminal:
curl http://localhost:3000/with-suspense
curl http://localhost:3000/suspense-around-body
curl http://localhost:3000/without-suspense
The build output shows /without-suspense as fully dynamic:
Route (app)
┌ ○ /
├ ○ /_not-found
├ ◐ /suspense-around-body
├ ◐ /with-suspense
└ ƒ /without-suspense
○ (Static) prerendered as static content
◐ (Partial Prerender) prerendered as static HTML with dynamic server-streamed content
ƒ (Dynamic) server-rendered on demand
Note that /without-suspense is marked as ƒ (Dynamic) rather than
◐ (Partial Prerender). The fact that /suspense-around-body — which also
has no meaningful static HTML shell — is marked as ◐ is highly suggestive
that unstable_instant = false should be treated the same way. At minimum,
the inconsistency between these two routes is wrong.
Expected: No runtime logs for any route. Static generation results should
be cached and served without re-running the prerender path on each
request.
Actual: The /without-suspense request produces these logs on the server:
[prerenderToStream] route: /without-suspense | isBuildTimePrerendering: undefined
[collectSegmentData] route: /without-suspense | isBuildTimePrerendering: undefined
The /with-suspense and /suspense-around-body requests produce no logs
(correct).