mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
codex/fallback-root-cache
9 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a777ebcfae |
test: retry waiting for ISR fallback in prefetch-static-shell (#98884)
- ISR prerenders can take a moment to propagate in `deploy`, longer than in `start`, so do a retry loop that waits for the fallback to go away before running tests that need it - ISR fallbacks are only upgraded when using adapters, skip the tests that care about those if using legacy deploy bc they'll fail otherwise |
||
|
|
9908328192 |
[PPF] Fix unnecessary runtime prefetch with prefetch={true} (#98651)
In, we #98278 attempted to fix runtime follow-ups for shells (if a static prefetch produced an insuffient shell). However, it introduced a regression in this scenario, where neither shells or prefetches use runtime data, so both of these links should use static prefetches: ```tsx <Link href="/static-param/one" prefetch={true} /> {/* Revealed later */} <Link href="/static-param/two" prefetch={true} /> ``` We'd correctly do a static prefetch for the first link. However, the second link would see a `RuntimeShell`-tier shell from the first link, and this check inside `isShellEntryEligibleForStaticAttempt` would fail (because `RuntimeShell` was now greater than `PPR`): https://github.com/vercel/next.js/blob/3c9d1ca77f7de845315dc166a57fc9f090c638b4/packages/next/src/client/components/segment-cache/scheduler.ts#L1261 as a result, `isShellEntryEligibleForStaticAttempt` would return `false`, and we would deopt to a runtime prefetch for the second link instead. Importantly, a page with `ensureStatic = "prefetch"`(which will be implemented in #98191) will currently be handled exactly like a page that did not use runtime data at build -- the client doesn't know that a page is meant to never use runtime requests at all, it's entirely based on the existing static hints/`needsRuntimeRequest` promise mechanism. So even if `/static/[slug]` sets `ensureStatic = "prefetch"`, we'd also do an unnecessary runtime prefetch for it even though we really shouldn't. Violating the `ensureStatic` contract seems worse than a missing runtime shell follow-up , because those can currently only happen if the shell starts using runtime data after a revalidation, and that's probably not a common scenario. So i'm partially reverting that change until we figure out how to handle this in a way that handles both cases correctly (i've attempted doing that, but it seems nontrivial) ### Testing We're now missing the runtime shell follow-up again, so tests related to that in `prefetch-app-shell-revalidation.test.ts` are marked as failing (but still assert on current behavior -- the follow-up failures can be reproduced by setting `REPRODUCE_MISSING_RUNTIME_SHELL_FOLLOW_UP=1`). Note that the runtime *prefetch* follow ups there work as expected -- those worked correctly even before #98278. I've also added some pretty comprehensive tests in `prefetch-static-shell.test.ts`. We now test the scenario that regressed, which we weren't doing before. We also test scenarios where we reveal multiple links, but some of them use runtime data in the prefetch, and some don't. There's some failures there related to the fact that we don't track runtime data accesses in shells and prefetches separately (revealed by `REPRODUCE_UNNECESSARY_RUNTIME_SHELL=1`), which will be addressed by #98129. I want to land that before doing anything else on this topic to avoid dealing with failures that are going to be fixed soon anyway. There's also one case (`REPRODUCE_UNNECESSARY_RUNTIME_PREFETCH=1`) which will not be fixed by it, and will need separate consideration. |
||
|
|
e974969ddd |
[PPF] Model caches with stale < MIN_SHELL_STALE as prefetch-only data (#98339)
Caches that have `stale < MIN_SHELL_STALE` were using `makeStageHangingPromise` in prerenders, which (in theory) was meant to track a runtime data access during a static prerender, which in turn would make the page use runtime shells. This is semantically incorrect, because both static and runtime shells would exclude the cache, and both static and runtime prefetches would include it, so it does not actually signal runtimeness, only prefetchness. The only thing we should be doing for these caches is excluding the content, same as `unstable_prefetch()`. This PR replaces `makeStageHangingPromise` with `makePrefetchHangingPromise`, which is not tracked as runtime data at all. We also use it for `unstable_prefetch()` to signal the correspondence. Note that in practice, the usage of `makeStageHangingPromise` in `use-cache-wrapper` was *technically* fine, because its runtime data tracking was effectively dead code. We only returned it if `stagedRendering.finalStage < stage`, but for static prerenders `stage = staticLinkDataStage = PrefetchStatic` and `finalStage = Static`, so the condition was never actually true, and we never actually tracked it as a runtime data access. However semantically it should still not be tracked as runtime data. (The condition *can* be true in a runtime prerender that only prerenders the shell. We don't do any tracking in runtime prerenders, so it was also a no-op). (this was extracted from #98129, where we separate runtime data access tracking into session data and URL data. i couldn't figure which one `makeStageHangingPromise` should be tracked as, but then i realized it shouldn't be tracked at all) |
||
|
|
d352f1740a |
[PPF] Only track runtime accesses when the promise is used (#97165)
Improves how we track accesses of runtime data in static prerenders (which affects whether a page will use static or runtime shells). Now, Instead of tracking it when the hanging promise is created, we'll track it when `then/catch/finally` is called on the promise (which we use as a signal for being used). The promise tracking required some novel work, because: - Using a proxy seems to cause `Promise.then` to show up in call stacks (we've had issues with proxies throwing off React's IO tracking before, i believe this is a similar problem) - patching `then/catch/finally` on the promise directly doesn't work, because `then` doesn't get called for native `await` The only way of doing this that i've found is a custom `Promise` subclass that tracks the access in its `then` implementation. This is what `TrackedPromise` does. To improve the debuggability, i've added some debug logs enabled by `NEXT_PRIVATE_DEBUG_RUNTIME_DATA=1` to detect when a page is deopted to runtime and what expression caused it. --- This fixes a false deopt caused by `createServerPathnameForMetadata` - when called on a route with fallback params, it would always track a fallback params access even if no icons/og images that actually use it exist. This meant that we'd never statically optimize any routes that had fallback params. After this PR, we only track a runtime data access if a metadata route that uses that params actually exists, in which case the `pathname` promise gets awaited. (as a future optimization, we should do the equivalent of vary params tracking on metadata routes to know if the params are used at all. also, the logic in `pathname.ts` is generally in need of cleanup) |
||
|
|
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.
|
||
|
|
6fbd61c152 |
[PPF] Do not mark complete shell requests as partial (#97503)
The router code did not respect the isPartial byte for runtime app shells for some reason, potentially leading to unnecessary speculative prefetch requests even if the shell was in fact complete. The comments say that the byte on those can't be trusted: https://github.com/vercel/next.js/blob/d00eb6b6c471c9c51ef997f643b049210d9940a5/packages/next/src/client/components/segment-cache/cache.ts#L2694-L2696 which is not true -- the byte is definitely set to partial when a shell contains holes (due to URL data or link data). Updates some tests that now no longer trigger a speculative prefetch because the shell is complete. (note that our tests use `prefetch = 'partial'` a lot, so some of them were triggering speculative prefetches even when they shouldn't have. see #97469 for more) |
||
|
|
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. |
||
|
|
dfa7f4f713 |
Attempt static prefetch before resorting to runtime (#96095)
If we're reasonably confident that a segment can be prefetched statically without omitting data that would have been included during a runtime prefetch (e.g. cookies), the client should attempt prefetch the segment statically instead of going straight to a runtime request. The decision for whether to do this is based on the ShouldAttemptStaticPrefetch added in previous steps. If it turns out the static response is not sufficient, then it will fall back to a runtime request. This makes prefetching cheaper for pages that are fully statically renderable. We can make the optimization better in the future with more reliable per-segment computation of the ShouldAttemptStaticPrefetch, but the current approach should at least work for fully static pages, which is what's most important. This optimization applies during both the Shell phase and the Speculative phase of the prefetching algorithm. |