mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
codex/fallback-root-query-keys
7 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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.
|
||
|
|
3cdb56c251 |
[PPF] unstable_prefetch() (#97622)
Implements `unstable_prefetch()`, which is intended for use in
`partialPrefetching`. `await unstable_prefetch()` excludes content from
the app shell -- it will only be available when using `prefetch={true}`
(speculative prefetch) or during navigations.
As a rule of thumb, `unstable_prefetch()` resolves whenever static
`params` would:
- in a static prerender
- but NOT the app shell extracted from it, which is param-less
- in a runtime prefetch (`prefetch={true}`)
- but NOT a runtime app shell, which is param-less
Note that `prefetch()` is URL data, so using it in an App Shell without
Suspense will trigger an instant insight.
### Implementation notes
`prefetch()` is treated like URL data, so it resolves in the
`PrefetchStatic/PrefetchRuntime` stages added in #96908. The
implementation is basically analogous to `unstable_navigation()` except
using different stages. i've considered abstracting them into one
implementation, but decided against that for now, we can deduplicate
later.
Error messages about URL data have not been updated to mention it yet --
we will do that as a follow up, along with docs.
`await prefetch()` does not count as a runtime data access, meaning that
it won't affect the static prefetch hint for a route. however `await
prefetch(); await cookies()` does deopt the route, because using a
speculative runtime prefetch would reveal more content. Note that this
may cause us to unnecessarily deopt a shell to runtime even if only the
speculative part of the content would be improved by a runtime request;
this is not a new issue, but it's something we should optimize.
|
||
|
|
1be0ab80c4 |
[PPF] unstable_navigation() (#96908)
`navigation()` is a new API that allows omitting contents from runtime
shells and runtime prefetches. Conceptually, the point is to express
that something is expensive to compute, so we shouldn't do it for
requests that may not get used (shells and prefetches). Notably, this
means that it's fine to include it in a static prerender -- it'll be
computed once and used for many requests, so it doesn't make sense to
exclude it.
## Implementation
The split in behavior across static and runtime prerenders is a
departure from how most of our APIs behave -- usually, if something
resolves statically, then it also resolves in "more complete" prerender.
Departing from this leads to some implementation complexity.
We include three new stages, used by two facets of the implementation:
```diff
export enum RenderStage {
Before = 1,
//
ShellStatic = 10,
+ PrefetchStatic = 11, <------- params, prefetch() [static prerenders]
+ NavigationStatic = 12 <------navigation() [static prerenders]
Static = 13, <--------------- finish accumulators [static prerenders]
//
ShellRuntime = 20,
Runtime = 21, <-------------- params, prefetch() [runtime prerenders]
+ NavigationRuntime = 22, <---- navigation() [runtime prerenders]
//
Dynamic = 30,
Abandoned = 40,
}
```
### NavigationRuntime
In runtime prerenders (or dev renders that simulate them),
`navigation()` resolves in `NavigationRuntime`
We only reach this stage in 1. the embedded runtime prerender produced
for Cached Navigations and 2. during dev/prod full staged renders --
runtime shells end in `ShellRuntime`, and runtime prefetches end in
`Runtime`.
Notably, this means that content gated behind `navigation()` is included
in the embedded runtime prefetch stream.
### PrefetchStatic & NavigationStatic
This is a helper stage added before `Static`. Static prefetches still
use the `Static` stage for their output. This new stage exists so that
we can resolve static `params` (and `prefetch()` when we implement it)
which the stage is named after) separately from `navigation()`, which
resolves in `NavigationStatic`, after which the prerender ends in
`Static`. This separation is important, because during static prerenders
we track whether or not runtime APIs are used (see
`trackRuntimeDataAccessed`) to determine if a runtime shell (or runtime
prefetch) might give us more content than the static ones. However, a
runtime shell/prefethc **would not resolve navigation()**, so `await
navigation(); await cookies()` would not reveal more content, and thus
shouldn't count as a usage that prevents static optimization.
We achieve this by checking the stage inside
`trackRuntimeDataAccessedImpl` and not tracking anything if we reached
the `NavigationStatic` stage.
### Behavior of shells and validation
As noted before, `navigation()` has an incompatible resolution order
between static and runtime prerenders. In #97040, we did some groundwork
to deal with this in validation.
Static prerenders resolve `navigation()`, which means that static shells
include content gated behind navigation(). This means that Static Shell
Validation allows them.
On the other hand, App shells **do not** resolve `navigation()`. This
leads to an inconsistency for Instant Validation -- a `await
navigation()` might be fine if a page is prefetched statically, but
would become blocking as soon as the page starts using runtime data and
switches to a runtime shell. To avoid this pitfall, we pessimistically
assume that any `navigation()` _might_ be part of a runtime
shell/prefetch, so any `navigation()` unguarded by Suspense will error
in IV.
In practice, this is handled analogously to static params: we do a dev
render with `needsAppShell: true`, which makes `navigation()` resolve in
`NavigationRuntime`, and then we use the `ShellRuntime` stage when
validating, which means that `navigation()` will be a hole. Note that
the discriminated error message logic currently only retries errors
using the `Runtime` stage, which won't have `navigation()` resolved
either, so it will be incorrectly reported as dynamic data. This will be
improved in a follow up.
|
||
|
|
5817bd1def | Anchor the async local storage instances to global symbols (#97255) | ||
|
|
3de2d1a213 |
Unify allow-runtime with Partial Prefetching (#96106)
Removes the "allow-runtime" prefetch config, and turns its behavior on implicitly wherever Partial Prefetching is enabled. The original motivation for "allow-runtime" was to give apps more control over server costs triggered by prefetches. Until a route explicitly opts in, prefetches would only be served from the CDN, not from the server. The problem, though, was it was very confusing to know when to add or remove this configuration. The incentive for many apps was to add it everywhere, with no clear signal for when to remove it. Our updated thinking is that Partial Prefetching itself already provides sufficient protection against runaway prefetching costs: per-link prefetches only happen on Link components that explicitly opt in with the prefetch prop. The optimizations landed earlier in this stack also make allow-runtime less necessary: on pages where all the content is statically renderable, prefetches are served from the static cache and no runtime request is ever issued; only a page that accesses non-static data is prefetched at runtime. The upshot of this decision is that runtime versus static becomes an internal optimization; the same content gets prefetched regardless of whether or how Next.js is able to optimize it. |
||
|
|
46681d90f0 |
[PPF] Sync IO is only allowed in the dynamic stage (#95384)
This PR makes Sync IO behavior more restrictive when `partialPrefetching` is on (either globally or for a page). - Previously, we allowed `await cookies(); Date.now()` in in segments that won't ever be runtime prefetched. This was achieved by separating segments into "early" and "late" stages and only erroring in the "early" ones. - After this PR, it will always be an error to do sync IO anywhere other than after `io()`/`connection()`/uncached IO (i.e. outside the Dynamic stage). We're doing this mainly because with `partialPrefetching`, `cookies()` resolves in App Shells, and sync IO causes a severe deopt in any prerender. `allow-runtime` also opts a route into `partialPrefetching`, which means that even the segments above the runtime prefetch boundary will need an App Shell and can't tolerate Sync IO. This PR removes all early/late stage separation, because we no longer need to vary the Sync IO behavior on the segment. Instead, we now pick whether a stageController should use - `SyncIOMode.AllowedInRuntimeOrDynamic` (legacy) - `SyncIOMode.AllowedInDynamic` (`partialPrefetching`) - `SyncIOMode.Untracked` if it needs to opt out I've removed the existing tests that asserted that sync IO is allowed in segments above the `allow-runtime` boundary. Instead, we check that sync io either errors (if partialPrefetching is on) or is allowed (if partialPrefetching is off) Closes NAR-855 |
||
|
|
c3dd28fd6a |
[ci] Split up large cache-components-dev-warmup test suite (#95553)
This splits two of our slowest test suites into smaller pieces that can be run in parallel and retried in isolation. ## Context Jest treats test suites as the smallest test execution unit. Slow test suites can lead to situations where a test shard is using very little CPU waiting for one last test suite to finish, and when tests fail and have to be retried, we have to retry the entire suite. Here's a particularly bad example: <img width="3116" height="2634" alt="Screenshot 2026-06-08 at 20-00-05 CI Telemetry — turbopack production tests (1)" src="https://github.com/user-attachments/assets/b5534200-4b77-40d5-a219-4b6fc552ed77" /> I instructed Claude to fetch test timing information from GitHub Actions Artifact, and to report the slowest test suites. > How timings work today > > - The `fetch-test-timings` job runs `node run-tests.js --timings --write-timings`, which pulls per-file durations from a Vercel KV store and uploads them as the test-timings artifact (test-timings.json, 5-day retention). > - Each sharded job downloads that artifact and run-tests.js -g N/M greedily bin-packs whole test files into groups by predicted duration (run-tests.js:528-549). > - I pulled the artifact from the July 7 canary run (28843797348): 1684 test files, ~61,500s total, median 20s, but max 1023s. > > Since the file is the unit of scheduling, a shard can never finish faster than its largest file. The job durations from that run confirm the imbalance this causes: test dev 4/10 took 28 min while siblings took 10–17; test cache components dev 6/6 took 13 min vs 5–6 for siblings; test turbopack production 5/7 took 20 min vs 10–11. > The slowest suites ``` ┌────────────────────┬────────────────────────────────────────────────────────────────────────────┐ │ Time │ Suite │ ├────────────────────┼────────────────────────────────────────────────────────────────────────────┤ │ 1023s + 933s │ test/development/app-dir/cache-components-dev-warmup/ (both variants) │ ├────────────────────┼────────────────────────────────────────────────────────────────────────────┤ │ 942s │ test/production/create-next-app/templates/matrix.test.ts │ ├────────────────────┼────────────────────────────────────────────────────────────────────────────┤ │ 788s + 432s + 232s │ test/e2e/app-dir/cache-components-errors/cache-components-errors.*.test.ts │ ├────────────────────┼────────────────────────────────────────────────────────────────────────────┤ │ 779s │ test/development/acceptance-app/rsc-build-errors.test.ts │ ├────────────────────┼────────────────────────────────────────────────────────────────────────────┤ │ 470s │ test/e2e/opentelemetry/instrumentation/opentelemetry.test.ts │ ├────────────────────┼────────────────────────────────────────────────────────────────────────────┤ │ 457s │ test/e2e/app-dir/actions/app-action-node-middleware.test.ts │ ├────────────────────┼────────────────────────────────────────────────────────────────────────────┤ │ 451s │ test/e2e/app-dir/next-after-app/index.test.ts │ ├────────────────────┼────────────────────────────────────────────────────────────────────────────┤ │ 447s │ test/e2e/app-dir/css-order/css-order.test.ts │ ├────────────────────┼────────────────────────────────────────────────────────────────────────────┤ │ 426s │ test/e2e/telemetry/config.test.ts │ ├────────────────────┼────────────────────────────────────────────────────────────────────────────┤ │ 402s │ test/production/debug-build-path/debug-build-paths.test.ts │ └────────────────────┴────────────────────────────────────────────────────────────────────────────┘ ``` > Reading these files, almost every one is slow for the same structural reason: a `describe.each(...)` over fixtures/modes/runtimes where each iteration boots its own `nextTestSetup` (dev server or full production build), run serially within one Jest worker: > - **cache-components-dev-warmup:** `describe.each` over 2 fixture dirs, and it restarts the dev server (stop/clean/start) before every one of ~17 tests. It's slow by design (cache-warmup isolation), so per-test cost can't shrink — but the file can be split. > - **create-next-app matrix:** `describe.each(app|pages)` × `it.each` over a ~16–48-combination flag matrix, each doing a full CNA scaffold (+ dev-server cycle for pages), fully serial. > - cache-components-errors.test.ts: 7490 lines, 129 tests, with per-describe `next build --experimental-build-mode compile` and per-path generate builds in prod mode. > - **css-order:** three sibling `describe.each` blocks over 4–6 chunking modes, each mode booting its own server, ~3 blocks × modes × page-pair orderings. > - **next-after-app, opentelemetry:** `describe.each` over runtimes/configs, again one server per iteration. |