-
Notifications
You must be signed in to change notification settings - Fork 26.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix navigation applying stale data when triggered from global not fou…
…nd (#62033) ### What When a global not found page is rendered, and when the not-found page or containing layout has a link with `prefetch: auto` back to the root page, the router would update the URL but not correctly swap out the not-found component with the page component. ### Why With auto prefetching (which is the default when `prefetch` is left unspecified on a link), the router will perform a partial prefetch on dynamic pages. This means it'll fetch the flight data _without_ React nodes and store it in the prefetch cache. On navigation, this is used to determine where we already have cached React nodes and where we need to trigger a lazy fetch to get new data. However, global not found pages are peculiar in that they will always contain a data path like: `['', { children: ['__PAGE__', {}] }]` since they are inserted at the root. This means that if there's also a page component that corresponds with the same path, the router will incorrectly think it already has cache node data for it. ### How During SSR when the `asNotFound` flag signals to the renderer that the component we're rendering is matching the global not-found page, we modify the segment key to be something unique so the data path won't collide with a top-level page. In [fc01c8e](fc01c8e) I added handling only on the server to modify the segment key. This still fixes the issue, but at the cost of triggering an MPA navigation on the client because it's treated as a root layout change In [69d5687](69d5687) I added client handling to not treat this special segment key as a root layout change, and to signal to the router it needs to refetch the data. This ensures we don't do an MPA navigation. Fixes #61956 Closes NEXT-2481 --------- Co-authored-by: JJ Kasper <jj@jjsweb.site>
- Loading branch information
Showing
7 changed files
with
98 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// we want the layout to opt-out of static prefetching | ||
export const dynamic = 'force-dynamic' | ||
|
||
import Link from 'next/link' | ||
|
||
export default function RootLayout({ | ||
children, | ||
}: { | ||
children: React.ReactNode | ||
}) { | ||
return ( | ||
<html lang="en"> | ||
<body> | ||
<Link href="/">Link to `/`</Link> | ||
<div>{children}</div> | ||
</body> | ||
</html> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import Link from 'next/link' | ||
|
||
export default function Home() { | ||
return ( | ||
<div> | ||
<h1>Home Page</h1> | ||
<Link href="/fake-link">Go to Invalid Page</Link> | ||
</div> | ||
) | ||
} |
40 changes: 40 additions & 0 deletions
40
test/e2e/app-dir/prefetching-not-found/prefetching-not-found.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { nextTestSetup } from 'e2e-utils' | ||
import { retry } from 'next-test-utils' | ||
|
||
describe('prefetching-not-found', () => { | ||
const { next } = nextTestSetup({ | ||
files: __dirname, | ||
}) | ||
|
||
it('should correctly navigate to/from a global 404 page when following links with prefetch=auto', async () => { | ||
let browser = await next.browser('/') | ||
expect(await browser.elementByCss('h1').text()).toBe('Home Page') | ||
|
||
await browser.elementByCss("[href='/fake-link']").click() | ||
|
||
await retry(async () => { | ||
expect(await browser.elementByCss('body').text()).toContain( | ||
'This page could not be found.' | ||
) | ||
}) | ||
|
||
await browser.elementByCss("[href='/']").click() | ||
|
||
await retry(async () => { | ||
expect(await browser.elementByCss('h1').text()).toBe('Home Page') | ||
}) | ||
|
||
// assert the same behavior, but starting at the not found page. This is to ensure that when we seed the prefetch cache, | ||
// we don't have any cache collisions that would cause the not-found page to remain rendered when following a link to the home page | ||
browser = await next.browser('/fake-link') | ||
expect(await browser.elementByCss('body').text()).toContain( | ||
'This page could not be found.' | ||
) | ||
|
||
await browser.elementByCss("[href='/']").click() | ||
|
||
await retry(async () => { | ||
expect(await browser.elementByCss('h1').text()).toBe('Home Page') | ||
}) | ||
}) | ||
}) |