mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
Based on: - https://github.com/vercel/next.js/pull/87203 - https://github.com/vercel/next.js/pull/87256 --- Currently, there's a cliff when navigating to a route that is unknown to the client. The prefetch cache is bypassed completely because we don't know the structure of the target route, and therefore we don't know which segments we might be able to use to construct a loading state (or even a fully cached response). However, because RSC responses are streaming, we can take advantage of the fact that the first chunk of the response contains the route tree. So as soon as we receive the first chunk, we can show a cached loading state to the user (if available), even while we wait for the rest of the response to arrive from the server. To access a segment's cache, you need its corresponding RouteTree. Previously this was not available during an unprefetched navigation because those objects were only created during a prefetch. But now that we've migrated the navigation implementation to use RouteTrees, too, we can access the cache directly inside the main navigation function, rather than having to do two separate passes like before. In other words, the only difference between a navigation to an unknown route versus a prefetched route is the extra latency required to get those first bytes from the server. Once the route tree resolves, the rest of the implementation and behavior is identical.
81 lines
2.8 KiB
TypeScript
81 lines
2.8 KiB
TypeScript
import { nextTestSetup } from 'e2e-utils'
|
|
import type * as Playwright from 'playwright'
|
|
import { createRouterAct } from 'router-act'
|
|
|
|
describe('mismatching prefetch', () => {
|
|
const { next, isNextDev } = nextTestSetup({
|
|
files: __dirname,
|
|
})
|
|
if (isNextDev) {
|
|
test('disabled in development', () => {})
|
|
return
|
|
}
|
|
|
|
function relativeHref(href: string) {
|
|
const url = new URL(href)
|
|
return url.pathname + url.search + url.hash
|
|
}
|
|
|
|
it(
|
|
'recovers when a navigation rewrites to a different route than the one ' +
|
|
'that was prefetched',
|
|
async () => {
|
|
let page: Playwright.Page
|
|
const browser = await next.browser('/mismatching-prefetch', {
|
|
beforePageLoad(p: Playwright.Page) {
|
|
page = p
|
|
},
|
|
})
|
|
const act = createRouterAct(page)
|
|
|
|
// Reveal the link to trigger a prefetch of page A.
|
|
const toggle = await browser.elementByCss(
|
|
'input[data-link-accordion="/mismatching-prefetch/dynamic-page/a?mismatch-rewrite=./b"]'
|
|
)
|
|
await act(async () => await toggle.click(), {
|
|
includes: 'Loading a...',
|
|
})
|
|
|
|
// When we click the link to navigate, the navigation will rewrite to
|
|
// a different route than the one that was prefetched.
|
|
await act(
|
|
async () => {
|
|
const link = await browser.elementByCss(
|
|
'a[href="/mismatching-prefetch/dynamic-page/a?mismatch-rewrite=./b"]'
|
|
)
|
|
await link.click()
|
|
// Immeidately after the click, the app navigates to the loading state
|
|
// that was prefetched, which is for page A.
|
|
const pageALoading = await browser.elementById(
|
|
'dynamic-page-loading-a'
|
|
)
|
|
expect(relativeHref(await browser.url())).toBe(
|
|
'/mismatching-prefetch/dynamic-page/a?mismatch-rewrite=./b'
|
|
)
|
|
expect(await pageALoading.text()).toBe('Loading a...')
|
|
|
|
// Simultaneously, the dynamic content for page A is requested.
|
|
},
|
|
// When the dynamic request is received, Next.js will discover that the
|
|
// route has changed and rewrite to page B. The client will detect a
|
|
// mismatch and render again using the new route from the server.
|
|
//
|
|
// This shouldn't require a second dynamic request, though, because we
|
|
// can reuse the data sent by the server. So, page B should only render
|
|
// once on the server.
|
|
[{ includes: 'Dynamic page b' }]
|
|
)
|
|
|
|
// The redirected page loads successfully.
|
|
const pageBContent = await browser.elementById('dynamic-page-content-b')
|
|
expect(await pageBContent.text()).toBe('Dynamic page b')
|
|
|
|
// The browser's URL hasn't changed, because this was a rewrite, not
|
|
// a redirect.
|
|
expect(relativeHref(await browser.url())).toBe(
|
|
'/mismatching-prefetch/dynamic-page/a?mismatch-rewrite=./b'
|
|
)
|
|
}
|
|
)
|
|
})
|