Skip to content

Commit 50e41a2

Browse files
authored
backport: fix prefetch bailout detection for nested loading segments (#70618)
Backports: - #67358 Fixes #70527 Note: Turbopack dev failures seem to be pre-existing on the base branch.
1 parent e19d91c commit 50e41a2

File tree

6 files changed

+40
-9
lines changed

6 files changed

+40
-9
lines changed

packages/next/src/server/app-render/walk-tree-with-flight-router-state.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,16 @@ export async function walkTreeWithFlightRouterState({
108108
// Explicit refresh
109109
flightRouterState[3] === 'refetch'
110110

111+
// Pre-PPR, the `loading` component signals to the router how deep to render the component tree
112+
// to ensure prefetches are quick and inexpensive. If there's no `loading` component anywhere in the tree being rendered,
113+
// the prefetch will be short-circuited to avoid requesting a potentially very expensive subtree. If there's a `loading`
114+
// somewhere in the tree, we'll recursively render the component tree up until we encounter that loading component, and then stop.
111115
const shouldSkipComponentTree =
112116
// loading.tsx has no effect on prefetching when PPR is enabled
113117
!experimental.ppr &&
114118
isPrefetch &&
115119
!Boolean(components.loading) &&
116-
(flightRouterState ||
117-
// If there is no flightRouterState, we need to check the entire loader tree, as otherwise we'll be only checking the root
118-
!hasLoadingComponentInTree(loaderTree))
120+
!hasLoadingComponentInTree(loaderTree)
119121

120122
if (!parentRendered && renderComponentsOnThisLevel) {
121123
const overriddenSegment =

test/e2e/app-dir/app-prefetch/app/page.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ export default function HomePage() {
88
<Link href="/static-page" id="to-static-page">
99
To Static Page
1010
</Link>
11+
<Link href="/prefetch-auto/foobar" id="to-dynamic-page">
12+
To Dynamic Slug Page
13+
</Link>
1114
</>
1215
)
1316
}

test/e2e/app-dir/app-prefetch/app/prefetch-auto/[slug]/layout.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,26 @@ import Link from 'next/link'
22

33
export const dynamic = 'force-dynamic'
44

5+
function getData() {
6+
const res = new Promise((resolve) => {
7+
setTimeout(() => {
8+
resolve({ message: 'Layout Data!' })
9+
}, 2000)
10+
})
11+
return res
12+
}
13+
514
export default async function Layout({ children }) {
15+
const result = await getData()
16+
617
return (
718
<div>
819
<h1>Layout</h1>
920
<Link prefetch={undefined} href="/prefetch-auto/justputit">
1021
Prefetch Link
1122
</Link>
1223
{children}
24+
<h3>{JSON.stringify(result)}</h3>
1325
</div>
1426
)
1527
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export default function Loading() {
2-
return <h1>Loading Prefetch Auto</h1>
2+
return <h1 id="loading-text">Loading Prefetch Auto</h1>
33
}

test/e2e/app-dir/app-prefetch/app/prefetch-auto/[slug]/page.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export const dynamic = 'force-dynamic'
33
function getData() {
44
const res = new Promise((resolve) => {
55
setTimeout(() => {
6-
resolve({ message: 'Hello World!' })
6+
resolve({ message: 'Page Data!' })
77
}, 2000)
88
})
99
return res
@@ -13,9 +13,9 @@ export default async function Page({ params }) {
1313
const result = await getData()
1414

1515
return (
16-
<>
16+
<div id="prefetch-auto-page-data">
1717
<h3>{JSON.stringify(params)}</h3>
1818
<h3>{JSON.stringify(result)}</h3>
19-
</>
19+
</div>
2020
)
2121
}

test/e2e/app-dir/app-prefetch/prefetching.test.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,8 @@ createNextDescribe(
263263
})
264264

265265
const prefetchResponse = await response.text()
266-
expect(prefetchResponse).not.toContain('Hello World')
266+
expect(prefetchResponse).toContain('Page Data')
267+
expect(prefetchResponse).not.toContain('Layout Data!')
267268
expect(prefetchResponse).not.toContain('Loading Prefetch Auto')
268269
})
269270

@@ -297,10 +298,23 @@ createNextDescribe(
297298
})
298299

299300
const prefetchResponse = await response.text()
300-
expect(prefetchResponse).not.toContain('Hello World')
301+
expect(prefetchResponse).not.toContain('Page Data!')
301302
expect(prefetchResponse).toContain('Loading Prefetch Auto')
302303
})
303304

305+
it('should immediately render the loading state for a dynamic segment when fetched from higher up in the tree', async () => {
306+
const browser = await next.browser('/')
307+
const loadingText = await browser
308+
.elementById('to-dynamic-page')
309+
.click()
310+
.waitForElementByCss('#loading-text')
311+
.text()
312+
313+
expect(loadingText).toBe('Loading Prefetch Auto')
314+
315+
await browser.waitForElementByCss('#prefetch-auto-page-data')
316+
})
317+
304318
describe('dynamic rendering', () => {
305319
describe.each(['/force-dynamic', '/revalidate-0'])('%s', (basePath) => {
306320
it('should not re-render layout when navigating between sub-pages', async () => {

0 commit comments

Comments
 (0)