Commit Graph

15 Commits

Author SHA1 Message Date
Hendrik Liebau 6866b9442c Keep never-prerenderable params out of self-hosted shells (#98612)
With Cache Components enabled and Partial Prefetching disabled, blocking
prerenders in `next start` could cache params that no
`generateStaticParams` result supplies. For `/[top]/[bottom]`, where
only `top` is supplied, this included `bottom` in the static HTML and
created a separate cache entry for each bottom value.

Adapter deployments already enforce this rule. The legacy Vercel builder
has a known limitation that the existing prerender tests explicitly
exclude.

#95872 established that these params must remain dynamic. #96297 later
put self-hosted eligibility-based shell keys and fallback-param
selection behind the `partialPrefetching` flag, alongside automatic
fallback upgrades. #98512 then made resumes honor the parameter set
recorded by the selected prerender. That did not create the invalid
prerenders, but allowed their over-resolved params to enter Cached
Navigations.

The RSC test added in #98512 did not account for that known
legacy-builder limitation and consequently failed in this [canary deploy
run](https://github.com/vercel/next.js/actions/runs/34659100427/job/103469344769).

For `next start`, shell keys and unresolved params now follow GSP
eligibility even when upgrades are disabled. Cache writes, revalidation,
and navigation RDC reads use the same key, including shared shells that
cannot be completed further. Routes whose params are all prerenderable
retain their existing keys when Partial Prefetching is disabled.
Automatic upgrade triggers remain gated.

The aforementioned `cached-navigations` tests now separate cold RSC
navigation from initial HTML and assert eligibility independently of the
observed response. Added coverage checks shell sharing, explicit
revalidation, and generated optional paths. Legacy-builder failures are
documented with expected-failure gates; its implementation remains
unchanged.

Verified by running the [full deploy test
matrix](https://github.com/vercel/next.js/actions/runs/34771777150)
against this PR.
2026-09-15 16:26:34 +02:00
Hendrik Liebau 1a295a631c Align fallback parameter staging with shell validation (#98512)
`next dev` reported missing Suspense boundaries in layouts that the
build accepted. For `/[top]/items/[bottom]`, `generateStaticParams`
returned `[{ top: 't1' }]`, and the page wrapped its `bottom` access in
Suspense. A request for `/t2/items/b2` still reported the layout's
access to `top` as an error. Development treated both parameters as
unresolved because the requested `top` value was not generated, although
the required static shell only needed to defer `bottom`.

Production Cached Navigations used the same overly broad parameter set
and omitted eligible static content from repeat visits. Resumes also
reconstructed that set from the original build manifest, even after an
on-demand prerender had produced a more complete shell.

This replaces the alternative proposed in #98460. That proposal fixes
the development error by selecting a separate fallback parameter set for
validation while keeping response staging unchanged. The two sets are
not intended to differ for the same shell target. Correcting only
validation would preserve the incorrect staging decision and leave
production Cached Navigations without the eligible static content.

Staging and static-shell validation now use one `stagedFallbackParams`
set for each selected shell target. Required partial shells retain their
unresolved parameters, even when a later request can complete them.
Prerenders record their parameter set in postponed state, and resumes
use that recorded set rather than reconstructing it from the generic
source.

Dynamic RSC requests now read and revalidate the completed-shell cache
key, so they find partial artifacts that the fully resolved pathname
lookup missed. Request metadata and `RequestStore` both expose the set
as `stagedFallbackParams`. Action-only fallback detection checks actual
unresolved parameters instead of treating deferred values as missing.
2026-09-11 10:46:54 +02:00
Josh Story e0966df114 test: migrate caching deploy exclusions to force gates (#98154)
## Summary

- migrate Cache Components, PPR, prefetching, prerendering,
revalidation, and SSG deployment exclusions to `@force-gate`
- remove the corresponding `skipDeployment` options and `skipped`
control flow while preserving each suite's rationale

This keeps caching-related exclusions together so their deployed
semantics can be reviewed by the same owners.

## Verification

- verified on the combined top-of-stack tree with `pnpm typescript`
- compared ordinary-mode collection before and after: all 4,670 existing
test names matched
- collected all 510 affected files in deploy mode: 4,578 skipped tests,
zero failures

<!-- NEXT_JS_LLM -->
2026-09-09 08:25:36 -07:00
Andrew Clark 3717cd3dfb Gate partial fallback shell upgrades behind partialPrefetching for next start (#96297)
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.
2026-07-29 19:56:49 +00:00
Jiwon Choi e5307e0a46 Restore partial fallback shell upgrade coverage (#96096)
### Why?

Deployment tests for `partial-fallback-shell-upgrade` repeatedly fail
after #96074 because the fixture still expects partial fallback shell
upgrades without opting into Partial Prefetching. #96074 intentionally
emits `partialFallback` metadata only when `partialPrefetching` is
enabled.

Failed deployment test:
https://github.com/vercel/next.js/actions/runs/30008120037/job/89223758358#step:32:464

Passed:
https://github.com/vercel/next.js/actions/runs/30031537314/job/89297370591#step:32:107

<!-- NEXT_JS_LLM -->
2026-07-23 20:50:04 +02:00
Niklas Mischkulnig da06078b9b test: Fix immutable static asset deployment tests for real (#95600)
https://github.com/vercel/next.js/pull/95550 wasn't enough:

-
https://github.com/vercel/next.js/actions/runs/28907225463/job/85769093204
-
https://github.com/vercel/next.js/actions/runs/28907225463/job/85769093252

Because `VERCEL_IMMUTABLE_STATIC_FILES_ENABLED` might be set, but it
still only works when using adapters, so it broke in the non-adapter
deploy test jobs.

Also prepare for when we switch the default of `NEXT_ENABLE_ADAPTER` by
setting it explicitly to `0` for when we want to test the old CLI
builder.
2026-07-08 14:16:35 +02:00
Josh Story d698d658d4 Remove partialFallbacks config flag (#93859)
Flag is already defaulted true. Removes option to disable
2026-05-15 08:34:19 +00:00
Zack Tanner 9895972da5 [partial fallbacks]: include prefetch requests in shell upgrade handling (#92894)
With the new `partialFallbacks` heuristic, routes that weren't part of
`generateStaticParams` will now upgrade their shells to include the
params as if they had been in `generateStaticParams`, but only when the
page with the unknown param was lazily visited.

This tweaks the heuristic to allow segment prefetches to also trigger
the upgrade behavior. This will ensure subsequent clients, when
prefetching the upgraded route, will receive the entry with params
filled in rather than showing the generic fallback.

In a subsequent PR, we'll make it so that the requesting client that
triggers the upgrade path can also benefit from the params being
upgraded, as the client router currently has no awareness that a
background revalidation completed.
2026-04-17 09:35:39 -07:00
Zack Tanner 8a1316e212 [partial fallbacks]: enable by default (#92627)
This makes `experimental.partialFallbacks` the default when
`cacheComponents` is enabled.

For routes that prerender only a subset of params via
`generateStaticParams`, requests for unknown params used to either
suspend to a nearby fallback or block until the full route finished
rendering. In the cases where the route could already produce a
contentful shell because param access happened below a Suspense
boundary, the old default still served that generic shell on every
request.

With `partialFallbacks` enabled by default, the first request still
receives the best shell available for that route, but Next.js now
revalidates in the background using the newly discovered param values.
That allows subsequent requests to reuse a more specific cached result:
- If the route becomes fully static for those params, later requests get
the fully prerendered page.
- If the leaf remains dynamic, Next.js upgrades the shell as far as it
can, filling in the newly known static params while the dynamic portion
continues to stream.

This also fixes a bug that was caught now that we ran this flag against
the entire CI. A concrete path like `/optional-catchall` could match the
generic `/optional-catchall/[[...slug]]` shell and incorrectly serve
fallback-shell behavior even though that exact path was already
prerendered. This change keeps concrete prerendered paths serving their
concrete result instead of downgrading back to the generic shell.

This PR also removes redundant `partialFallbacks: true` test config and
adds coverage for the explicit opt-out behavior (`partialFallbacks:
false`).
2026-04-10 14:39:25 -07:00
Zack Tanner c291479011 partial fallbacks: adapter support for intermediate shells (#91902)
Previously, `partialFallback` was only emitted when all dynamic params
could become fully concrete after upgrade. For a route like
`/prefix/[one]/[two]` where only `[one]` has generateStaticParams, the
feature was disabled entirely.

This relaxes the guard to allow adapters to produce an intermediate
shell for not fully known params, and tweaks the handling to support
when both known and unknown params are provided to the render.
Concretely:

- When partial `nxtP*` params are provided during background
revalidation but `normalizeDynamicRouteParams `rejects them (missing
params), merge the provided params into the existing params to override
placeholders.
- Compute `effectiveFallbackRouteParams` by filtering to only params
that still have placeholder values. Use this filtered set for
`fallbackRouteParams` so intermediate shells render resolved params in
the static shell and only suspend for truly unknown params
2026-04-01 09:49:59 -07:00
Zack Tanner ae3f9f5187 partial fallbacks: complete generic shells into more specific shells (#91231)
In #91158, I wired up the plumbing to indicate whether a partial
fallback shell is upgradeable. The previous heuristic was too coarse.
This wires up the handling in `next start`.

This PR teaches the runtime to complete a fallback shell only as far as
prerendering allows. Instead of promoting a generic shell like
`/[one]/[two]` directly to `/c/foo`, it promotes it to the most specific
prerendered shell for that branch, `/c/[two]`, where `[one]` has
`generateStaticParams`. That means later requests can reuse the more
complete shell, while fully dynamic params continue to stream as dynamic
content.

With this change:

- the first request still serves the generic fallback shell immediately
- the background revalidate writes a more specific shell, not a fully
concrete route
- later requests for the same prerendered branch reuse that shell
- upgrading stops once only fully dynamic params remain

This keeps the runtime behavior aligned with the build-time
`remainingPrerenderableParams` contract from the downstack PR.
2026-03-13 18:21:20 -07:00
Zack Tanner cf4d3ae89c partial fallbacks: only upgrade shells with remaining prerenderable params (#91158)
Partial fallback upgrading is currently too broad. Any PPR fallback shell can be treated as upgradeable, including routes with no `generateStaticParams` and shells that are already as specific as prerendering can make them.

In practice, that means we preserve `allowQuery` and enable background promotion for shells that should remain as partial fallbacks. That broadens the feature beyond the medium-cardinality case it was meant for and makes the build output imply per-param upgradeability when there are no prerenderable params left to fill.

This PR teaches static-path generation to compute `remainingPrerenderableParams` for each fallback shell: the unresolved pathname params that can still be filled by `generateStaticParams` in that branch. We then use that signal to gate partial fallback behavior.

With this change:

- `partialFallback` is only emitted for shells that still have prerenderable params remaining
- `allowQuery` with params is only preserved for those shells

Effectively, this limits partial fallback upgrading to shells that can still be made more specific by prerendering.
2026-03-13 17:35:14 -07:00
Zack Tanner 5ef117cdf3 Revert "Exclude partial fallback for deploy mode" (#91172)
Reverts vercel/next.js#91163

Flag is re-landed for test teams
2026-03-12 10:56:07 -07:00
Zack Tanner c0bf4b6f00 partial fallbacks: gate behind experimental flag (#91123)
This puts the partial fallback upgrading feature introduced in
https://github.com/vercel/next.js/pull/89063 behind a flag to make it
easier to test the rollout/eventual stabilization in isolation.
2026-03-09 15:58:25 -07:00
Zack Tanner af48a18368 re-introduce partial fallback -> route upgrading (#89063)
In https://github.com/vercel/next.js/pull/69282, we landed support for
Partial Fallback Prerendering. The intention behind this feature was to
be able to upgrade from a partial fallback (essentially the UI produced
when `await params` is dynamic, ie the param was not part of
`generateStaticParams` at build time) to a route shell (when the param
values are filled in, and thus we can prerender more of the page).

It was reverted in https://github.com/vercel/next.js/pull/79258 because
we didn't add first-class support to this in the build output API and
the necessary infrastructure changes to perform similar behavior that
this PR does to trigger a background revalidation with params filled in.

This re-lands the feature in Next.js now that upstream support landed
(https://github.com/vercel/vercel/pull/14703,
https://github.com/vercel/vercel/pull/15338, and Vercel infra changes
which are rolling out)
2026-03-06 08:53:43 -08:00