From 96b1b06d0b538039f0929ea1db06cf72c20d11a5 Mon Sep 17 00:00:00 2001
From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
Date: Fri, 28 Aug 2026 21:33:13 +0200
Subject: [PATCH 1/8] Turbopack: drop rust analyzer skip annotation (#98047)
---
Cargo.lock | 2 ++
turbopack/crates/turbo-tasks-macros/src/value_impl_macro.rs | 1 -
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/Cargo.lock b/Cargo.lock
index 4a2ba8198d7b..6f2ca1fef51a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3169,6 +3169,8 @@ version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
dependencies = [
+ "allocator-api2",
+ "equivalent",
"foldhash 0.1.5",
"serde",
]
diff --git a/turbopack/crates/turbo-tasks-macros/src/value_impl_macro.rs b/turbopack/crates/turbo-tasks-macros/src/value_impl_macro.rs
index 3958967ea044..401b7b0d5e25 100644
--- a/turbopack/crates/turbo-tasks-macros/src/value_impl_macro.rs
+++ b/turbopack/crates/turbo-tasks-macros/src/value_impl_macro.rs
@@ -286,7 +286,6 @@ pub fn value_impl(args: TokenStream, input: TokenStream) -> TokenStream {
}
quote! {
// Register this `impl Trait for Concrete` into the link-time `TRAIT_IMPLS_SLICE`.
- #[cfg(not(rust_analyzer))]
turbo_tasks::macro_helpers::scattered_collect::declarative::scatter! {
#[scatter(turbo_tasks::macro_helpers::TRAIT_IMPLS_SLICE)]
const _: turbo_tasks::macro_helpers::TraitImplRecord = {
From 357e514cf7bdc276bfd830003c38027c17d9de05 Mon Sep 17 00:00:00 2001
From: Janka Uryga
Date: Fri, 28 Aug 2026 21:41:32 +0200
Subject: [PATCH 2/8] Revert "test: re-enable sync IO root coverage" (#98050)
Reverts vercel/next.js#97996
The fix does not seem to help with the flakiness.
---
.../app-dir/sync-io-blocks-root/sync-io-blocks-root.test.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/test/production/app-dir/sync-io-blocks-root/sync-io-blocks-root.test.ts b/test/production/app-dir/sync-io-blocks-root/sync-io-blocks-root.test.ts
index d4cddaa87a62..a7e57f6df3b9 100644
--- a/test/production/app-dir/sync-io-blocks-root/sync-io-blocks-root.test.ts
+++ b/test/production/app-dir/sync-io-blocks-root/sync-io-blocks-root.test.ts
@@ -1,6 +1,8 @@
import { nextTestSetup } from 'e2e-utils'
-describe('sync IO that blocks the root', () => {
+// TODO(lubieowoce): reenable when the cause of flakiness is found and fixed
+// (seems to have increased sharply around 2026-08-24/25)
+describe.skip('sync IO that blocks the root', () => {
const { next } = nextTestSetup({
files: __dirname,
skipStart: true,
From d8f95c12de0c8874b775346029a5c547b784a2cb Mon Sep 17 00:00:00 2001
From: Janka Uryga
Date: Fri, 28 Aug 2026 22:10:21 +0200
Subject: [PATCH 3/8] [PPF] Fix navigation() in prospective runtime prerenders
(#98000)
### Long story short
Fixes an analogous bug to #95665, but affecting
`prefetch()/navigation()`. Those two were accidentally always resolving
in the **prospective** runtime prerender, so we were warming content
that isn't needed for the final runtime prerender. We now guard them
properly.
Also, I replaced `PrerenderStoreModernRuntime.isSessionShell` with
`finalStage` (the same stage we're gonna use for the final prerender),
so we can include/exclude `navigation` properly -- the boolean only
distinguished shell/prefetch, not navigation.
### Long story long
We use runtime prerenders for three things: runtime shells, runtime
prefetches, and embedding a prefetch in navigations. These are aborted
after `ShellRuntime`, `Runtime`, and `NavigationRuntime` respectively
(accessible in `stageController.finalStage`). If we're doing the
**final** prerender for a shell, promises that would resolve in e.g.
`NavigationRuntime` would never resolve, because we abort the prerender
before we get there.
However, the **prospective** runtime prerender has no
`StagedRenderingController` and needs to model this separately (so that
the same APIs hang in both). This was [previously done via
`prerenderStore.isSessionShell`](https://github.com/vercel/next.js/pull/95665)
which we checked in URL data apis i.e. `params/searchParams` and the
metadata`pathname` to do this. `isSessionShell: true` meant they should
hang because it's a shell, and `isSessionShell: false` meant they should
resolve.
However, I forgot about this case when implementing
`navigation`/`prefetch`, and made them resolve unconditionally in the
prospective prerender. This means we'd potentially warm content that
would not actually be reached in the final prerender. In other words,
```ts
await navigation()
return
```
would needlessly render `` during the prospective render
but not in the final one. This kinda defeats `navigation`'s main
purpose, i.e. avoiding expensive work.
This PR fixes the bug by replacing `isSessionShell: boolean` with
`finalStage: AdvanceableRenderStage`, which is set to the same value we
put in `stageController.finalStage`. This lets us keep the logic of what
hangs and what resolves relatively close in both prerenders.
---
On a meta level, the fact that the same bug happened twice tells me that
having a `StageRenderingController` that is sometimes `null` is
error-prone, and we should figure out a better solution, but i'm gonna
keep this fix targeted and figure out a more holistic solution in a
follow-up
---
.../next/src/server/app-render/app-render.tsx | 77 +++---
.../work-unit-async-storage.external.ts | 7 +-
.../next/src/server/request/cache-stages.ts | 31 ++-
packages/next/src/server/request/params.ts | 35 +--
packages/next/src/server/request/pathname.ts | 4 +-
.../next/src/server/request/search-params.ts | 21 +-
.../app/page.tsx | 20 --
.../prefetch-app-shell-cached-gsp.test.ts | 70 ------
.../app/layout.tsx | 10 +-
.../app/lazy-data-in-prefetch/page.tsx | 38 +++
.../app/lazy-data-in-shell/page.tsx | 38 +++
.../app/page.tsx | 35 +++
.../app/pass-navigation-to-cache/page.tsx | 36 +++
.../app/pass-prefetch-to-cache/page.tsx | 36 +++
.../app/slug/[slug]/page.tsx | 0
.../components/link-accordion.tsx | 0
.../next.config.ts | 0
.../runtime-prerender-cache-warming.test.ts | 237 ++++++++++++++++++
18 files changed, 523 insertions(+), 172 deletions(-)
delete mode 100644 test/e2e/app-dir/segment-cache/prefetch-app-shell-cached-gsp/app/page.tsx
delete mode 100644 test/e2e/app-dir/segment-cache/prefetch-app-shell-cached-gsp/prefetch-app-shell-cached-gsp.test.ts
rename test/e2e/app-dir/segment-cache/{prefetch-app-shell-cached-gsp => runtime-prerender-cache-warming}/app/layout.tsx (57%)
create mode 100644 test/e2e/app-dir/segment-cache/runtime-prerender-cache-warming/app/lazy-data-in-prefetch/page.tsx
create mode 100644 test/e2e/app-dir/segment-cache/runtime-prerender-cache-warming/app/lazy-data-in-shell/page.tsx
create mode 100644 test/e2e/app-dir/segment-cache/runtime-prerender-cache-warming/app/page.tsx
create mode 100644 test/e2e/app-dir/segment-cache/runtime-prerender-cache-warming/app/pass-navigation-to-cache/page.tsx
create mode 100644 test/e2e/app-dir/segment-cache/runtime-prerender-cache-warming/app/pass-prefetch-to-cache/page.tsx
rename test/e2e/app-dir/segment-cache/{prefetch-app-shell-cached-gsp => runtime-prerender-cache-warming}/app/slug/[slug]/page.tsx (100%)
rename test/e2e/app-dir/segment-cache/{prefetch-app-shell-cached-gsp => runtime-prerender-cache-warming}/components/link-accordion.tsx (100%)
rename test/e2e/app-dir/segment-cache/{prefetch-app-shell-cached-gsp => runtime-prerender-cache-warming}/next.config.ts (100%)
create mode 100644 test/e2e/app-dir/segment-cache/runtime-prerender-cache-warming/runtime-prerender-cache-warming.test.ts
diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx
index 27e19ac968fe..7329af702e90 100644
--- a/packages/next/src/server/app-render/app-render.tsx
+++ b/packages/next/src/server/app-render/app-render.tsx
@@ -1598,17 +1598,6 @@ async function generateRuntimePrefetchResult(
// but we're not going to persist this anywhere.
const prerenderResumeDataCache = createPrerenderResumeDataCache()
- await prospectiveRuntimeServerPrerender(
- ctx,
- isShellPrefetch,
- generateDynamicRSCPayload.bind(null, ctx),
- prerenderResumeDataCache,
- rootParams,
- requestStore.headers,
- requestStore.cookies,
- requestStore.draftMode
- )
-
const mode: RuntimePrerenderMode = isShellPrefetch
? {
type: 'session-shell-only',
@@ -1620,6 +1609,17 @@ async function generateRuntimePrefetchResult(
shellByteLengthDeferred: createPromiseWithResolvers(),
}
+ await prospectiveRuntimeServerPrerender(
+ ctx,
+ getFinalStageForRuntimePrerenderMode(mode),
+ generateDynamicRSCPayload.bind(null, ctx),
+ prerenderResumeDataCache,
+ rootParams,
+ requestStore.headers,
+ requestStore.cookies,
+ requestStore.draftMode
+ )
+
const debugChannel = setReactDebugChannel
? createWebDebugChannel()
: undefined
@@ -1656,7 +1656,7 @@ async function generateRuntimePrefetchResult(
async function prospectiveRuntimeServerPrerender(
ctx: AppRenderContext,
- isShellPrefetch: boolean,
+ finalStage: AdvanceableRenderStage,
getPayload: () => Promise,
resumeDataCache: PrerenderResumeDataCache | null,
rootParams: Params,
@@ -1705,9 +1705,10 @@ async function prospectiveRuntimeServerPrerender(
hmrRefreshHash: undefined,
// We don't track vary params during initial prerender, only the final one
varyParamsAccumulator: null,
- // No stage sequencing needed for prospective renders.
+ // We don't use sequencing needed for prospective renders, but we need to
+ // track where the final prerender is going to end to avoid warming too much.
stagedRendering: null,
- isSessionShell: isShellPrefetch,
+ finalStage,
// These are not present in regular prerenders, but allowed in a runtime
// prerender.
// Any cache keyed on headers() or cookies() needs to be invalidated.
@@ -1854,19 +1855,8 @@ async function finalRuntimeServerPrerender(
isDebugDynamicAccesses
)
- let finalStage: AdvanceableRenderStage
- switch (mode.type) {
- case 'session-shell-only':
- finalStage = RenderStage.ShellRuntime
- break
- case 'rewindable-session-shell':
- finalStage = RenderStage.Runtime
- break
- case 'navigation':
- finalStage = RenderStage.NavigationRuntime
- break
- }
- const finalStageController = new StagedRenderingController({
+ const finalStage = getFinalStageForRuntimePrerenderMode(mode)
+ const stageController = new StagedRenderingController({
abortSignal: finalServerController.signal,
abandonController: null,
// In dynamic renders, we allow Sync IO in the Runtime stage
@@ -1898,8 +1888,8 @@ async function finalRuntimeServerPrerender(
resumeDataCache,
hmrRefreshHash: undefined,
varyParamsAccumulator,
- stagedRendering: finalStageController,
- isSessionShell: mode.type === 'session-shell-only',
+ stagedRendering: stageController,
+ finalStage,
// These are not present in regular prerenders, but allowed in a runtime
// prerender.
headers: HeadersAdapter.fresh(headers),
@@ -1923,7 +1913,7 @@ async function finalRuntimeServerPrerender(
collectPrerenderChunk(collectedChunks, finalServerController.signal, chunk)
increaseChunkByteLengths(
stageByteLengths,
- finalStageController.currentStage,
+ stageController.currentStage,
chunk.byteLength
)
}
@@ -1963,7 +1953,7 @@ async function finalRuntimeServerPrerender(
await runInSequentialTasks(
async () => {
- finalStageController.advanceStage(RenderStage.ShellStatic)
+ stageController.advanceStage(RenderStage.ShellStatic)
let stream = workUnitAsyncStorage.run(
finalServerPrerenderStore,
@@ -1989,19 +1979,19 @@ async function finalRuntimeServerPrerender(
},
() => {
if (checkUnexpectedAbort()) return
- finalStageController.advanceStage(RenderStage.PrefetchStatic)
+ stageController.advanceStage(RenderStage.PrefetchStatic)
},
() => {
if (checkUnexpectedAbort()) return
- finalStageController.advanceStage(RenderStage.NavigationStatic)
+ stageController.advanceStage(RenderStage.NavigationStatic)
},
() => {
if (checkUnexpectedAbort()) return
- finalStageController.advanceStage(RenderStage.Static)
+ stageController.advanceStage(RenderStage.Static)
},
() => {
if (checkUnexpectedAbort()) return
- finalStageController.advanceStage(RenderStage.ShellRuntime)
+ stageController.advanceStage(RenderStage.ShellRuntime)
},
() => {
if (checkUnexpectedAbort()) return
@@ -2009,7 +1999,7 @@ async function finalRuntimeServerPrerender(
// We may not reach this stage depending on the mode.
if (finalStage < RenderStage.Runtime) return
- finalStageController.advanceStage(RenderStage.Runtime)
+ stageController.advanceStage(RenderStage.Runtime)
},
() => {
if (checkUnexpectedAbort()) return
@@ -2040,7 +2030,7 @@ async function finalRuntimeServerPrerender(
// We may not reach this stage depending on the mode.
if (finalStage < RenderStage.NavigationRuntime) return
- finalStageController.advanceStage(RenderStage.NavigationRuntime)
+ stageController.advanceStage(RenderStage.NavigationRuntime)
},
() => {
// Finish the accumulators. We need to wait for Flight to flush the result into the stream,
@@ -2090,6 +2080,19 @@ async function finalRuntimeServerPrerender(
}
}
+function getFinalStageForRuntimePrerenderMode(
+ mode: RuntimePrerenderMode
+): AdvanceableRenderStage {
+ switch (mode.type) {
+ case 'session-shell-only':
+ return RenderStage.ShellRuntime
+ case 'rewindable-session-shell':
+ return RenderStage.Runtime
+ case 'navigation':
+ return RenderStage.NavigationRuntime
+ }
+}
+
/**
* Crawlers will inadvertently think the canonicalUrl in the RSC payload should be crawled
* when our intention is to just seed the router state with the current URL.
diff --git a/packages/next/src/server/app-render/work-unit-async-storage.external.ts b/packages/next/src/server/app-render/work-unit-async-storage.external.ts
index 69b35b95902f..7674db24fead 100644
--- a/packages/next/src/server/app-render/work-unit-async-storage.external.ts
+++ b/packages/next/src/server/app-render/work-unit-async-storage.external.ts
@@ -16,7 +16,10 @@ import type { Params } from '../request/params'
import type { ImplicitTags } from '../lib/implicit-tags'
import type { WorkStore } from './work-async-storage.external'
import { InvariantError } from '../../shared/lib/invariant-error'
-import type { StagedRenderingController } from './staged-rendering'
+import type {
+ AdvanceableRenderStage,
+ StagedRenderingController,
+} from './staged-rendering'
import type { ValidationBoundaryTracking } from './instant-validation/boundary-tracking'
import type { InstantValidationSampleTracking } from './instant-validation/instant-samples'
@@ -226,7 +229,7 @@ export interface PrerenderStoreModernRuntime
* renders where all stages run without sequencing.
*/
readonly stagedRendering: StagedRenderingController | null
- readonly isSessionShell: boolean
+ readonly finalStage: AdvanceableRenderStage
readonly headers: RequestStore['headers']
readonly cookies: RequestStore['cookies']
diff --git a/packages/next/src/server/request/cache-stages.ts b/packages/next/src/server/request/cache-stages.ts
index b1ebfb99265e..2850ecacf0f3 100644
--- a/packages/next/src/server/request/cache-stages.ts
+++ b/packages/next/src/server/request/cache-stages.ts
@@ -5,6 +5,7 @@ import {
} from '../app-render/work-unit-async-storage.external'
import {
applyOwnerStack,
+ makeUntrackedHangingPromise,
RENDER_STAGES_BY_DATA_KIND,
trackIncompatibleShellContent,
} from '../dynamic-rendering-utils'
@@ -52,6 +53,7 @@ export function unstable_prefetch(): Promise {
const { stagedRendering } = workUnitStore
if (!stagedRendering) {
// Prospective prerender
+ // `unstable_prefetch()` will resolve in the final prerender, so resolve it here as well.
return Promise.resolve(undefined)
} else {
// Final prerender
@@ -69,13 +71,23 @@ export function unstable_prefetch(): Promise {
// Note that this does not mark the subtree as dynamic -- content guarded by
// prefetch() is still considered cacheable.
const { stagedRendering } = workUnitStore
+ const prefetchStage = RENDER_STAGES_BY_DATA_KIND.runtimeLinkData
if (!stagedRendering) {
// Prospective prerender
- return Promise.resolve(undefined)
+ // Make sure we don't unblock content that won't be reached in the final prerender.
+ if (workUnitStore.finalStage < prefetchStage) {
+ return makeUntrackedHangingPromise(
+ workUnitStore.renderSignal,
+ workStore.route,
+ '`unstable_prefetch()`'
+ )
+ } else {
+ return Promise.resolve(undefined)
+ }
} else {
// Final prerender
return stagedRendering.delayUntilStage(
- RENDER_STAGES_BY_DATA_KIND.runtimeLinkData,
+ prefetchStage,
'unstable_prefetch',
undefined
)
@@ -194,6 +206,7 @@ export function unstable_navigation(): Promise {
const { stagedRendering } = workUnitStore
if (!stagedRendering) {
// Prospective prerender
+ // `unstable_navigation()` will resolve in the final prerender, so resolve it here as well.
return Promise.resolve(undefined)
} else {
// Final prerender
@@ -211,13 +224,23 @@ export function unstable_navigation(): Promise {
// Note that this does not mark the subtree as dynamic -- content guarded by
// navigation() is still considered cacheable.
const { stagedRendering } = workUnitStore
+ const navigationStage = RenderStage.NavigationRuntime
if (!stagedRendering) {
// Prospective prerender
- return Promise.resolve(undefined)
+ // Make sure we don't unblock content that won't be reached in the final prerender.
+ if (workUnitStore.finalStage < navigationStage) {
+ return makeUntrackedHangingPromise(
+ workUnitStore.renderSignal,
+ workStore.route,
+ '`unstable_navigation()`'
+ )
+ } else {
+ return Promise.resolve(undefined)
+ }
} else {
// Final prerender
return stagedRendering.delayUntilStage(
- RenderStage.NavigationRuntime,
+ navigationStage,
'unstable_navigation',
undefined
)
diff --git a/packages/next/src/server/request/params.ts b/packages/next/src/server/request/params.ts
index 9e01ada7c870..d8e417ceca77 100644
--- a/packages/next/src/server/request/params.ts
+++ b/packages/next/src/server/request/params.ts
@@ -427,36 +427,27 @@ function createRuntimePrerenderParams(
return makeUntrackedParams(userspaceParams)
}
+ // Root params are allowed in shells, so we allow them to resolve without a delay.
+ if (allParamsAreRootParams(underlyingParams, workUnitStore.rootParams)) {
+ return makeUntrackedParams(userspaceParams)
+ }
+
+ // Non-root params are URL data, and we need to recover a param-less session shell,
+ // so we delay all params until the runtime stage (even if they're static)
+ const paramsStage = RENDER_STAGES_BY_DATA_KIND.runtimeLinkData
+
const { stagedRendering } = workUnitStore
if (!stagedRendering) {
- // If there's no staging, we're in a prospective runtime prerender.
- if (workUnitStore.isSessionShell) {
- // If we're warming up for a session shell, params should be hanging,
- // because they'll be a hanging input in the final prerender.
+ // If there's no stage controller, we're in a prospective runtime prerender.
+ // Make sure we don't unblock content that won't be reached in the final prerender.
+ if (workUnitStore.finalStage < paramsStage) {
return makeHangingParams(underlyingParams, workStore, workUnitStore)
} else {
return makeUntrackedParams(userspaceParams)
}
}
- // We don't have fallbackParams in runtime prerenders, so we don't know
- // when params are static. However, root params are static by definition,
- // so we can at least check for that.
- // Note that resolving them without a delay is valid because root params are
- // allowed in shells.
- if (allParamsAreRootParams(underlyingParams, workUnitStore.rootParams)) {
- return makeUntrackedParams(userspaceParams)
- }
-
- // Semantically, we should resolve static params in the static stage.
- // But params are link data, and we need to recover a param-less session shell,
- // so we delay all params until the runtime stage instead.
- const staticParamsStage = RENDER_STAGES_BY_DATA_KIND.runtimeLinkData
- return stagedRendering.delayUntilStage(
- staticParamsStage,
- 'params',
- userspaceParams
- )
+ return stagedRendering.delayUntilStage(paramsStage, 'params', userspaceParams)
}
function createRenderParamsForPage(
diff --git a/packages/next/src/server/request/pathname.ts b/packages/next/src/server/request/pathname.ts
index dd78fea607ea..167b14d1c20c 100644
--- a/packages/next/src/server/request/pathname.ts
+++ b/packages/next/src/server/request/pathname.ts
@@ -57,15 +57,15 @@ export function createServerPathnameForMetadata(
// behavior of always resolving in the runtime stage
// (i.e. assuming that we have non-static params in the pathname)
const { stagedRendering } = workUnitStore
+ const pathnameStage = RENDER_STAGES_BY_DATA_KIND.runtimeLinkData
if (stagedRendering) {
- const pathnameStage = RENDER_STAGES_BY_DATA_KIND.runtimeLinkData
return stagedRendering.delayUntilStage(
pathnameStage,
undefined,
underlyingPathname
)
} else {
- if (workUnitStore.isSessionShell) {
+ if (workUnitStore.finalStage < pathnameStage) {
return makeDynamicHangingPromise(
workUnitStore.renderSignal,
workStore.route,
diff --git a/packages/next/src/server/request/search-params.ts b/packages/next/src/server/request/search-params.ts
index 3f0760fdae4c..ce5d9688de2f 100644
--- a/packages/next/src/server/request/search-params.ts
+++ b/packages/next/src/server/request/search-params.ts
@@ -245,16 +245,20 @@ function createRuntimePrerenderSearchParams(
: underlyingSearchParams
const result = makeUntrackedSearchParams(userspaceSearchParams)
+
+ const searchParamsStage = RENDER_STAGES_BY_DATA_KIND.runtimeLinkData
+
const { stagedRendering } = workUnitStore
if (!stagedRendering) {
- // If there's no staging, we're in a prospective runtime prerender.
- if (workUnitStore.isSessionShell) {
- // If we're warming up for a session shell, search params should hang,
- // because they'll be a hanging input in the final prerender.
+ // If there's no stage controller, we're in a prospective runtime prerender.
+ // Make sure we don't unblock content that won't be reached in the final prerender.
+ if (workUnitStore.finalStage < searchParamsStage) {
return makeHangingSearchParams(workStore, workUnitStore)
+ } else {
+ return result
}
- return result
}
+
// Unlike `createRuntimePrerenderParams`, which uses `delayUntilStage`, we
// resolve with `waitForStage(...).then(...)` here. Switching search params to
// `delayUntilStage` drops the source code frame from the instant-validation
@@ -264,7 +268,6 @@ function createRuntimePrerenderSearchParams(
// `suspense-boundaries` tests. The underlying reason in React's async I/O
// await tracking isn't understood yet. TODO: align search params with params
// on `delayUntilStage` once resolved.
- const searchParamsStage = RENDER_STAGES_BY_DATA_KIND.runtimeLinkData
return stagedRendering.waitForStage(searchParamsStage).then(() => result)
}
@@ -382,9 +385,9 @@ function makeHangingSearchParams(
prerenderStore.renderSignal,
workStore.route,
'`searchParams`',
- // This promise is created for every page whether or not it reads search
- // params, so recording the access at creation would mark every render.
- // The access is tracked in the proxy traps below instead.
+ // Passing `null` for the store disables tracking of params usage.
+ // We want accesses of chained promises to be tracked as well.
+ // TODO: The custom tracking seems unnecessary, we should standardize it
null
)
diff --git a/test/e2e/app-dir/segment-cache/prefetch-app-shell-cached-gsp/app/page.tsx b/test/e2e/app-dir/segment-cache/prefetch-app-shell-cached-gsp/app/page.tsx
deleted file mode 100644
index 78e4e16658da..000000000000
--- a/test/e2e/app-dir/segment-cache/prefetch-app-shell-cached-gsp/app/page.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { LinkAccordion } from '../components/link-accordion'
-
-export default function Page() {
- return (
-
-
- -
-
- prerendered from gSP - /slug/prerendered
-
-
- -
-
- not prerendered - /slug/not-prerendered
-
-
-
-
- )
-}
diff --git a/test/e2e/app-dir/segment-cache/prefetch-app-shell-cached-gsp/prefetch-app-shell-cached-gsp.test.ts b/test/e2e/app-dir/segment-cache/prefetch-app-shell-cached-gsp/prefetch-app-shell-cached-gsp.test.ts
deleted file mode 100644
index a28caeeb88b3..000000000000
--- a/test/e2e/app-dir/segment-cache/prefetch-app-shell-cached-gsp/prefetch-app-shell-cached-gsp.test.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { nextTestSetup } from 'e2e-utils'
-import type * as Playwright from 'playwright'
-import { createRouterAct } from 'router-act'
-import { retry } from 'next-test-utils'
-
-const CACHE_MISS_WARNING = 'Unexpected cache miss after cache warming phase'
-
-// The App Shell prefetch (next-router-prefetch: '3') renders the runtime shell
-// of the /slug/[slug] route. Because the page is a `'use cache'` page that
-// awaits static params, params are a hanging input in the final prerender. If
-// the prospective (cache-warming) prerender resolved those params instead of
-// leaving them hanging, the cached page's key would differ between the two
-// prerenders and the final prerender would log an "Unexpected cache miss"
-// warning and degrade the cached segment to a dynamic hole.
-describe('App Shell prefetching - cached page with generateStaticParams', () => {
- const { next, isNextDev } = nextTestSetup({
- files: __dirname,
- })
- if (isNextDev) {
- it('is skipped', () => {})
- return
- }
-
- it('does not report a cache miss when prefetching the shell of a cached page that reads static params', async () => {
- let page: Playwright.Page
- const browser = await next.browser('/', {
- beforePageLoad(p: Playwright.Page) {
- page = p
- },
- })
- const act = createRouterAct(page, { includeAppShellRequests: true })
-
- // Revealing the link prefetches the App Shell for /slug/[slug]. Because the
- // shell is param-independent, this single request drives the runtime shell
- // prerender of the cached page whose params are a hanging input.
- await act(async () => {
- await browser
- .elementByCss('input[data-link-accordion="/slug/prerendered"]')
- .click()
- })
-
- expect(next.cliOutput).not.toContain(CACHE_MISS_WARNING)
- })
-
- it('renders the cached page content after navigating to a prefetched shell', async () => {
- let page: Playwright.Page
- const browser = await next.browser('/', {
- beforePageLoad(p: Playwright.Page) {
- page = p
- },
- })
- const act = createRouterAct(page, { includeAppShellRequests: true })
-
- await act(async () => {
- await browser
- .elementByCss('input[data-link-accordion="/slug/prerendered"]')
- .click()
- })
-
- await browser.elementByCss('a[href="/slug/prerendered"]').click()
-
- await retry(async () => {
- expect(await browser.elementById('slug').text()).toEqual(
- 'Slug: prerendered'
- )
- })
-
- expect(next.cliOutput).not.toContain(CACHE_MISS_WARNING)
- })
-})
diff --git a/test/e2e/app-dir/segment-cache/prefetch-app-shell-cached-gsp/app/layout.tsx b/test/e2e/app-dir/segment-cache/runtime-prerender-cache-warming/app/layout.tsx
similarity index 57%
rename from test/e2e/app-dir/segment-cache/prefetch-app-shell-cached-gsp/app/layout.tsx
rename to test/e2e/app-dir/segment-cache/runtime-prerender-cache-warming/app/layout.tsx
index fa1546fe2d01..3e61d2fa2638 100644
--- a/test/e2e/app-dir/segment-cache/prefetch-app-shell-cached-gsp/app/layout.tsx
+++ b/test/e2e/app-dir/segment-cache/runtime-prerender-cache-warming/app/layout.tsx
@@ -2,12 +2,10 @@ import { Suspense } from 'react'
import { cookies } from 'next/headers'
// Reading a cookie makes the shell depend on session data, so a prefetch
-// renders a runtime App Shell. This is what exercises the prospective/final
-// runtime prerender where params are a hanging input in a cached page.
+// renders a runtime App Shell.
async function SessionData() {
- const cookieStore = await cookies()
- const value = cookieStore.get('testCookie')?.value ?? 'none'
- return {`Cookie: ${value}`}
+ await cookies()
+ return null
}
export default async function Layout({ children }: LayoutProps<'/'>) {
@@ -17,7 +15,7 @@ export default async function Layout({ children }: LayoutProps<'/'>) {
Loading cookie...
}>
- {children}
+ {children}