mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
In #96074 I put the `partialFallback` behavior behind the `partialPrefetching` flag, so that we don't risk an explosion in ISR costs for apps that haven't opted into Partial Prefetching. But it turns out I only handled this for deploy mode — i.e. the build output consumed by the Vercel adapter. The behavior was still unconditionally on for `next start`. The reason it was easy to miss is that `next start` and the adapter express partial fallback shells through completely separate mechanisms. The adapter emits a `partialFallback` flag into the build output and lets the platform's ISR layer perform the shell upgrade. `next start` has no such flag; the server performs the upgrade itself, in the compiled page runtime. So the gate I added to the adapter output had no effect on the self-hosted path. To understand how the `next start` path was configured, I looked at the PR that removed the original top-level `partialFallbacks` config flag (#93859). Before that PR, the behavior was gated in the runtime; when the flag was removed those gates were deleted and the behavior became always-on. The important subtlety is that only the *upgrade* should be gated, not the shell machinery as a whole. On `next start`, the value that decides whether a shell can be specialized (`remainingPrerenderableParams`) also drives core Cache Components behavior — build-time partial prerendering of params and serving build-time sub-shells — which must stay on regardless of `partialPrefetching`. So this gates only the two things that make up the "upgrade on first request" cost risk: the background ISR revalidation that specializes a shell per request, and the client-facing `isFallbackUpgradeable` signal that tells the client to retry a prefetch waiting for that upgrade. Adds coverage with a `partialPrefetching`-disabled fixture asserting that fallback shells stay shared rather than specializing per request.
42 lines
1.7 KiB
TypeScript
42 lines
1.7 KiB
TypeScript
import { LinkAccordion } from '../components/link-accordion'
|
|
|
|
export default function Page() {
|
|
return (
|
|
<ul>
|
|
<li>
|
|
{/* Prerendered at build time via generateStaticParams — the prefetch
|
|
receives the concrete version, never a fallback. */}
|
|
<LinkAccordion href="/static-posts/1" prefetch={true}>
|
|
Static post 1 (concrete)
|
|
</LinkAccordion>
|
|
</li>
|
|
<li>
|
|
{/* Has generateStaticParams (for a different value), so this
|
|
un-enumerated param is upgradeable: the server serves a fallback
|
|
shell on first prefetch, then regenerates the concrete version in
|
|
the background. Used by the recovery test. */}
|
|
<LinkAccordion href="/posts/recovery" prefetch={true}>
|
|
Post recovery (upgradeable fallback)
|
|
</LinkAccordion>
|
|
</li>
|
|
<li>
|
|
{/* No generateStaticParams — the fallback shell can never be upgraded,
|
|
so the prefetch shell must NOT be flagged as a fallback (no retry). */}
|
|
<LinkAccordion href="/no-static-params/1" prefetch={true}>
|
|
No static params 1 (non-upgradeable fallback)
|
|
</LinkAccordion>
|
|
</li>
|
|
<li>
|
|
{/* Same upgradeable route as /posts/recovery, but with its own
|
|
un-enumerated param so its ISR entry is independent of the recovery
|
|
test's. Used by the retry-*limit* test, which forces every prefetch
|
|
response to keep returning the fallback so the client retries to
|
|
its cap and then stops. */}
|
|
<LinkAccordion href="/posts/retry-limit" prefetch={true}>
|
|
Post retry-limit (upgradeable fallback)
|
|
</LinkAccordion>
|
|
</li>
|
|
</ul>
|
|
)
|
|
}
|