mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
When navigating to a route that wasn't previously prefetched (e.g. via a
Link with prefetch={false}, or router.push()), the client stores the
route data from the server response in the cache so it can be reused by
future navigations and prefetches to the same URL.
These entries were being stored with a null "nextUrl" cache key, but
lookups use the real nextUrl from router state, so every subsequent
lookup missed. This caused redundant requests for route data the client
already had.
The fix threads the nextUrl through the route discovery path so it
reaches the cache write with the correct value. For non-intercepted
routes the entry is keyed with a wildcard (reusable regardless of
nextUrl); for intercepted routes it uses the specific nextUrl, since the
route structure varies by referrer.
Also cleans up how interception routes interact with the optimistic
route matching system: patterns are now always stored during discovery
(previously they were skipped), and the interception bailout happens in
a single place during matching rather than being duplicated across
multiple check sites.
Closes #88863
Co-authored-by: Hendrik Liebau <[email protected]>
Co-authored-by: Hamidreza Hanafi <[email protected]>
---------
Co-authored-by: Hendrik Liebau <[email protected]>
Co-authored-by: Hamidreza Hanafi <[email protected]>
97 lines
3.4 KiB
TypeScript
97 lines
3.4 KiB
TypeScript
import { nextTestSetup } from 'e2e-utils'
|
|
import type * as Playwright from 'playwright'
|
|
import { createRouterAct } from 'router-act'
|
|
|
|
describe('force stale', () => {
|
|
const { next, isNextDev } = nextTestSetup({
|
|
files: __dirname,
|
|
})
|
|
if (isNextDev) {
|
|
test('prefetching is disabled in dev', () => {})
|
|
return
|
|
}
|
|
|
|
it(
|
|
'during a navigation, don\'t request segments that have a pending "full" ' +
|
|
'prefetch already in progress',
|
|
async () => {
|
|
let act: ReturnType<typeof createRouterAct>
|
|
const browser = await next.browser('/', {
|
|
beforePageLoad(p: Playwright.Page) {
|
|
act = createRouterAct(p)
|
|
},
|
|
})
|
|
|
|
await act(
|
|
async () => {
|
|
// Reveal a link to a dynamic page. The Link has prefetch={true}, so the
|
|
// full page data is prefetched, including dynamic content.
|
|
const toggleLinkVisibility = await browser.elementByCss(
|
|
'input[data-link-accordion="/dynamic"]'
|
|
)
|
|
await act(async () => await toggleLinkVisibility.click(), {
|
|
includes: 'Dynamic page content',
|
|
// Block the data from loading into the client so we can test what
|
|
// happens if we request the same segment again during a navigation.
|
|
block: true,
|
|
})
|
|
|
|
// Initiate a navigation to the dynamic page. Even though the dynamic
|
|
// content from the prefetch hasn't loaded yet, the router should not
|
|
// request the same segment again, because it knows the data it
|
|
// receives from the prefetch will be complete. This assumption is
|
|
// _only_ correct for "full" prefetches, because we explicitly instruct
|
|
// the server not to omit any dynamic or runtime data.
|
|
const link = await browser.elementByCss('a[href="/dynamic"]')
|
|
await link.click()
|
|
},
|
|
// There should have been no additional requests upon navigation
|
|
'no-requests'
|
|
)
|
|
|
|
// The data succesfully streams in.
|
|
const content = await browser.elementById('dynamic-page-content')
|
|
expect(await content.text()).toBe('Dynamic page content')
|
|
}
|
|
)
|
|
|
|
it(
|
|
'during a "full" prefetch, read from bfcache before issuing new ' +
|
|
'prefetch request',
|
|
async () => {
|
|
let act: ReturnType<typeof createRouterAct>
|
|
const browser = await next.browser('/', {
|
|
beforePageLoad(p: Playwright.Page) {
|
|
act = createRouterAct(p)
|
|
},
|
|
})
|
|
|
|
// Navigate to the dynamic page using the link without prefetch.
|
|
// This will fetch the page data and store it in the bfcache.
|
|
await act(
|
|
async () => {
|
|
const link = await browser.elementById('link-without-prefetch')
|
|
await link.click()
|
|
},
|
|
{
|
|
includes: 'Dynamic page content',
|
|
}
|
|
)
|
|
|
|
// Navigate back to the home page
|
|
await browser.back()
|
|
|
|
// Now reveal a link with prefetch={true} to the same page. Because we've
|
|
// already navigated to this page, the route entry and segment data
|
|
// should already be in the cache. The prefetch should reuse the cached
|
|
// data instead of making a new request to the server.
|
|
await act(async () => {
|
|
const toggleLinkVisibility = await browser.elementByCss(
|
|
'input[data-link-accordion="/dynamic"]'
|
|
)
|
|
await toggleLinkVisibility.click()
|
|
}, 'no-requests')
|
|
}
|
|
)
|
|
})
|