-
Notifications
You must be signed in to change notification settings - Fork 27k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix router prefetch cache key to work with route interception (#59861)
### What? When handling route interception in two different segments but handled by the same interception route, the first interception will show the correct component but attempting the same interception on another segment will return elements from the first request, not the second. ### Why? Prefetch cache entries are created from the browser URL. However, route interception makes use of `nextUrl` to mask the underlying components that are being fetched from the server to handle the request Consider the following scenario: ``` app foo @modal (...)post [id] bar @modal (...post) [id] post [id] ``` If you trigger an interception on `/foo`, your URL is going to be masked to `/post/1` and keyed as such in the prefetch cache. However, the cache entry is actually associated with `app/foo/@modal/(...post)/[id]`. That means when you trigger the same interception on `/bar`, it will return the tree from `/foo`. ### How? This PR will prefix the prefetch cache key with `state.nextUrl` when necessary. Fixes #49878 Fixes #52748 Closes NEXT-1818
- Loading branch information
Showing
18 changed files
with
239 additions
and
13 deletions.
There are no files selected for viewing
29 changes: 29 additions & 0 deletions
29
packages/next/src/client/components/router-reducer/reducers/create-prefetch-cache-key.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,29 @@ | ||
import { addPathPrefix } from '../../../../shared/lib/router/utils/add-path-prefix' | ||
import { pathHasPrefix } from '../../../../shared/lib/router/utils/path-has-prefix' | ||
import { createHrefFromUrl } from '../create-href-from-url' | ||
|
||
/** | ||
* Creates a cache key for the router prefetch cache | ||
* | ||
* @param url - The URL being navigated to | ||
* @param nextUrl - an internal URL, primarily used for handling rewrites. Defaults to '/'. | ||
* @return The generated prefetch cache key. | ||
*/ | ||
export function createPrefetchCacheKey(url: URL, nextUrl: string | null) { | ||
const pathnameFromUrl = createHrefFromUrl( | ||
url, | ||
// Ensures the hash is not part of the cache key as it does not impact the server fetch | ||
false | ||
) | ||
|
||
// delimit the prefix so we don't conflict with other pages | ||
const nextUrlPrefix = `${nextUrl}%` | ||
|
||
// Route interception depends on `nextUrl` values which aren't a 1:1 mapping to a URL | ||
// The cache key that we store needs to use `nextUrl` to properly distinguish cache entries | ||
if (nextUrl && !pathHasPrefix(pathnameFromUrl, nextUrl)) { | ||
return addPathPrefix(pathnameFromUrl, nextUrlPrefix) | ||
} | ||
|
||
return pathnameFromUrl | ||
} |
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
27 changes: 27 additions & 0 deletions
27
test/e2e/app-dir/interception-route-prefetch-cache/app/Modal.tsx
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,27 @@ | ||
'use client' | ||
|
||
import { useRouter } from 'next/navigation' | ||
|
||
interface ModalProps { | ||
title: string | ||
context: string | ||
} | ||
|
||
export function Modal({ title, context }: ModalProps) { | ||
const router = useRouter() | ||
|
||
return ( | ||
<div> | ||
<div className="modal"> | ||
<h1>{title}</h1> | ||
<h2>{context}</h2> | ||
</div> | ||
<div | ||
className="modal-overlay" | ||
onClick={() => { | ||
router.back() | ||
}} | ||
/> | ||
</div> | ||
) | ||
} |
11 changes: 11 additions & 0 deletions
11
test/e2e/app-dir/interception-route-prefetch-cache/app/bar/@modal/(...)post/[id]/page.tsx
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,11 @@ | ||
import { Modal } from '../../../../Modal' | ||
|
||
export default function BarPagePostInterceptSlot({ | ||
params: { id }, | ||
}: { | ||
params: { | ||
id: string | ||
} | ||
}) { | ||
return <Modal title={`Post ${id}`} context="Intercepted on Bar Page" /> | ||
} |
3 changes: 3 additions & 0 deletions
3
test/e2e/app-dir/interception-route-prefetch-cache/app/bar/@modal/default.tsx
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,3 @@ | ||
export default function Default() { | ||
return null | ||
} |
3 changes: 3 additions & 0 deletions
3
test/e2e/app-dir/interception-route-prefetch-cache/app/bar/default.tsx
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,3 @@ | ||
export default function Default() { | ||
return null | ||
} |
14 changes: 14 additions & 0 deletions
14
test/e2e/app-dir/interception-route-prefetch-cache/app/bar/layout.tsx
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,14 @@ | ||
export default function BarLayout({ | ||
modal, | ||
children, | ||
}: { | ||
modal: React.ReactNode | ||
children: React.ReactNode | ||
}) { | ||
return ( | ||
<> | ||
<div id="slot">{modal}</div> | ||
<div id="children">{children}</div> | ||
</> | ||
) | ||
} |
10 changes: 10 additions & 0 deletions
10
test/e2e/app-dir/interception-route-prefetch-cache/app/bar/page.tsx
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 BarPage() { | ||
return ( | ||
<div> | ||
<h1>Bar Page</h1> | ||
<Link href="/post/1">Post 1</Link> | ||
</div> | ||
) | ||
} |
11 changes: 11 additions & 0 deletions
11
test/e2e/app-dir/interception-route-prefetch-cache/app/foo/@modal/(...)post/[id]/page.tsx
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,11 @@ | ||
import { Modal } from '../../../../Modal' | ||
|
||
export default function FooPagePostInterceptSlot({ | ||
params: { id }, | ||
}: { | ||
params: { | ||
id: string | ||
} | ||
}) { | ||
return <Modal title={`Post ${id}`} context="Intercepted on Foo Page" /> | ||
} |
3 changes: 3 additions & 0 deletions
3
test/e2e/app-dir/interception-route-prefetch-cache/app/foo/@modal/default.tsx
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,3 @@ | ||
export default function Default() { | ||
return null | ||
} |
3 changes: 3 additions & 0 deletions
3
test/e2e/app-dir/interception-route-prefetch-cache/app/foo/default.tsx
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,3 @@ | ||
export default function Default() { | ||
return null | ||
} |
14 changes: 14 additions & 0 deletions
14
test/e2e/app-dir/interception-route-prefetch-cache/app/foo/layout.tsx
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,14 @@ | ||
export default function FooLayout({ | ||
modal, | ||
children, | ||
}: { | ||
modal: React.ReactNode | ||
children: React.ReactNode | ||
}) { | ||
return ( | ||
<> | ||
<div id="slot">{modal}</div> | ||
<div id="children">{children}</div> | ||
</> | ||
) | ||
} |
10 changes: 10 additions & 0 deletions
10
test/e2e/app-dir/interception-route-prefetch-cache/app/foo/page.tsx
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 FooPage() { | ||
return ( | ||
<div> | ||
<h1>Foo Page</h1> | ||
<Link href="/post/1">Post 1</Link> | ||
</div> | ||
) | ||
} |
13 changes: 13 additions & 0 deletions
13
test/e2e/app-dir/interception-route-prefetch-cache/app/layout.tsx
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,13 @@ | ||
import Link from 'next/link' | ||
|
||
export default function RootLayout({ children }) { | ||
return ( | ||
<html> | ||
<head /> | ||
<body> | ||
<Link href="/">home</Link> | ||
{children} | ||
</body> | ||
</html> | ||
) | ||
} |
18 changes: 18 additions & 0 deletions
18
test/e2e/app-dir/interception-route-prefetch-cache/app/page.tsx
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,18 @@ | ||
import Link from 'next/link' | ||
|
||
export default function Home() { | ||
return ( | ||
<ul> | ||
<li> | ||
<Link href="/foo">foo</Link> | ||
</li> | ||
<li> | ||
<Link href="/bar">bar</Link> | ||
</li> | ||
<br /> | ||
<li> | ||
<Link href="/post/1">post 1</Link> | ||
</li> | ||
</ul> | ||
) | ||
} |
13 changes: 13 additions & 0 deletions
13
test/e2e/app-dir/interception-route-prefetch-cache/app/post/[id]/page.tsx
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,13 @@ | ||
export default function PostPage({ | ||
params: { id }, | ||
}: { | ||
params: { | ||
id: string | ||
} | ||
}) { | ||
return ( | ||
<div> | ||
<h1>Post {id}</h1> | ||
</div> | ||
) | ||
} |
45 changes: 45 additions & 0 deletions
45
test/e2e/app-dir/interception-route-prefetch-cache/interception-route-prefetch-cache.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,45 @@ | ||
import { createNextDescribe } from 'e2e-utils' | ||
import { check } from 'next-test-utils' | ||
|
||
createNextDescribe( | ||
'interception-route-prefetch-cache', | ||
{ | ||
files: __dirname, | ||
}, | ||
({ next }) => { | ||
it('should render the correct interception when two distinct layouts share the same path structure', async () => { | ||
const browser = await next.browser('/') | ||
|
||
await browser.elementByCss('[href="/foo"]').click() | ||
|
||
await check(() => browser.elementById('children').text(), /Foo Page/) | ||
|
||
await browser.elementByCss('[href="/post/1"]').click() | ||
|
||
// Ensure the existing page content is still the same | ||
await check(() => browser.elementById('children').text(), /Foo Page/) | ||
|
||
// Verify we got the right interception | ||
await check( | ||
() => browser.elementById('slot').text(), | ||
/Intercepted on Foo Page/ | ||
) | ||
|
||
// Go back home. At this point, the router cache should have content from /foo | ||
// Now we want to ensure that /bar doesn't share that same prefetch cache entry | ||
await browser.elementByCss('[href="/"]').click() | ||
await browser.elementByCss('[href="/bar"]').click() | ||
|
||
await check(() => browser.elementById('children').text(), /Bar Page/) | ||
await browser.elementByCss('[href="/post/1"]').click() | ||
|
||
// Ensure the existing page content is still the same. If the prefetch cache resolved the wrong cache node | ||
// then we'd see the content from /foo | ||
await check(() => browser.elementById('children').text(), /Bar Page/) | ||
await check( | ||
() => browser.elementById('slot').text(), | ||
/Intercepted on Bar Page/ | ||
) | ||
}) | ||
} | ||
) |