Gate partial fallback shell upgrades behind partialPrefetching for next start - #96297
Merged
Conversation
Contributor
Tests PassedCommit: 8739f7c |
Contributor
Stats from current PR✅ No significant changes detected📊 All Metrics📖 Metrics GlossaryDev Server Metrics:
Build Metrics:
Change Thresholds:
⚡ Dev Server
📦 Dev Server (Webpack) (Legacy)📦 Dev Server (Webpack)
⚡ Production Builds
📦 Production Builds (Webpack) (Legacy)📦 Production Builds (Webpack)
📦 Bundle SizesBundle Sizes⚡ TurbopackClient Main Bundles
Server Middleware
Build DetailsBuild Manifests
📦 WebpackClient Main Bundles
Polyfills
Pages
Server Edge SSR
Middleware
Build DetailsBuild Manifests
Build Cache
🔄 Shared (bundler-independent)Runtimes
📎 Tarball URLCommit: 8739f7c |
acdlite
force-pushed
the
partial-fallback-next-start
branch
2 times, most recently
from
July 28, 2026 20:12
ec56528 to
8a04a01
Compare
partialPrefetching for next startpartialPrefetching for next start
acdlite
force-pushed
the
partial-fallback-next-start
branch
2 times, most recently
from
July 29, 2026 17:08
fd9aa37 to
13c3227
Compare
acdlite
marked this pull request as ready for review
July 29, 2026 18:08
ztanner
reviewed
Jul 29, 2026
| }, 'no-requests') | ||
| }) | ||
|
|
||
| it('retries a static fallback prefetch a bounded number of times, then stops', async () => { |
Member
There was a problem hiding this comment.
Should this test run under a fixture with partialPrefetching: true? With the current config, isUpgradeableISRFallback should always be false, so the retry loop never starts and the < 5 assertion can pass with only the initial request.
ztanner
approved these changes
Jul 29, 2026
…`next start` 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. For `next start` the behavior we want is exactly what existed before the `experimental.partialFallbacks` config flag was removed in #93859. That PR deleted a coherent set of gates — the build-time shell metadata plus the runtime cache-key, fallback-mode, and background-upgrade decisions — and made the behavior always-on. This restores those same gates, keyed on `partialPrefetching` instead of the deleted flag. When Partial Prefetching is off, a fallback shell is served from the shared route shell (the normal ISR cache key) and never specialized or upgraded per request; when it's on, the shell specializes and upgrades exactly as it does today. The gates have to be restored as a set. Gating only the background upgrade (and leaving the cache key still specializing the shell) writes the specialized entry under a key that nothing ever populates, which breaks `revalidatePath` / `revalidateTag` for the affected routes. Restoring the cache-key gate alongside it keeps the entry on the normal ISR path. Tests: adds a `partialPrefetching`-disabled fixture asserting fallback shells stay shared, and updates the `next start` suites that exercise the upgrade to opt into `partialPrefetching` so they keep testing it: `sub-shell-generation-middleware` (only in its Cache Components mode, since `partialPrefetching` requires Cache Components), `cache-components-allow- otel-spans`, and `prefetch-fallback-retry`. The last also sets `prefetch={true}` on its links, because a Partial Prefetching app skips the speculative prefetch otherwise — without it the client never retries and the bounded-retry assertion would pass vacuously.
acdlite
force-pushed
the
partial-fallback-next-start
branch
from
July 29, 2026 19:27
13c3227 to
8739f7c
Compare
acdlite
enabled auto-merge (squash)
July 29, 2026 19:50
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
In #96074 I put the
partialFallbackbehavior behind thepartialPrefetchingflag, 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 fornext start.The reason it was easy to miss is that
next startand the adapter express partial fallback shells through completely separate mechanisms. The adapter emits apartialFallbackflag into the build output and lets the platform's ISR layer perform the shell upgrade.next starthas 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 startpath was configured, I looked at the PR that removed the original top-levelpartialFallbacksconfig 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 ofpartialPrefetching. 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-facingisFallbackUpgradeablesignal 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.