Skip to content

Commit

Permalink
Ensure client cache keys match between prefetch and transition
Browse files Browse the repository at this point in the history
  • Loading branch information
ijjk committed Jun 28, 2022
1 parent a72e136 commit 7fd4081
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 18 deletions.
37 changes: 19 additions & 18 deletions packages/next/shared/lib/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1199,7 +1199,7 @@ export default class Router implements BaseRouter {
router: this,
}))

if (!isMiddlewareMatch && shouldResolveHref && pathname !== '/_error') {
if (shouldResolveHref && pathname !== '/_error') {
;(options as any)._shouldResolveHref = true

if (process.env.__NEXT_HAS_REWRITES && as.startsWith('/')) {
Expand Down Expand Up @@ -1251,7 +1251,7 @@ export default class Router implements BaseRouter {

let route = removeTrailingSlash(pathname)

if (!isMiddlewareMatch && isDynamicRoute(route)) {
if (isDynamicRoute(route)) {
const parsedAs = parseRelativeUrl(resolvedAs)
const asPathname = parsedAs.pathname

Expand All @@ -1267,7 +1267,7 @@ export default class Router implements BaseRouter {
(param) => !query[param]
)

if (missingParams.length > 0) {
if (missingParams.length > 0 && !isMiddlewareMatch) {
if (process.env.NODE_ENV !== 'production') {
console.warn(
`${
Expand Down Expand Up @@ -2022,30 +2022,31 @@ export default class Router implements BaseRouter {
parsed.pathname = pathname
url = formatWithValidation(parsed)
}
} else {
parsed.pathname = resolveDynamicRoute(parsed.pathname, pages)

if (parsed.pathname !== pathname) {
pathname = parsed.pathname
parsed.pathname = pathname
Object.assign(
query,
getRouteMatcher(getRouteRegex(parsed.pathname))(asPath) || {}
)
url = formatWithValidation(parsed)
}
}
parsed.pathname = resolveDynamicRoute(parsed.pathname, pages)

// Prefetch is not supported in development mode because it would trigger on-demand-entries
if (process.env.NODE_ENV !== 'production') {
return
if (isDynamicRoute(parsed.pathname)) {
pathname = parsed.pathname
parsed.pathname = pathname
Object.assign(
query,
getRouteMatcher(getRouteRegex(parsed.pathname))(
parsePath(asPath).pathname
) || {}
)
url = formatWithValidation(parsed)
}

const locale =
typeof options.locale !== 'undefined'
? options.locale || undefined
: this.locale

// Prefetch is not supported in development mode because it would trigger on-demand-entries
if (process.env.NODE_ENV !== 'production') {
return
}

// TODO: if the route middleware's data request
// resolves to is not an SSG route we should bust the cache
// but we shouldn't allow prefetch to keep triggering
Expand Down
93 changes: 93 additions & 0 deletions test/integration/dynamic-routing/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,99 @@ const appDir = join(__dirname, '../')
const buildIdPath = join(appDir, '.next/BUILD_ID')

function runTests({ dev, serverless }) {
if (!dev) {
it('should have correct cache entries on prefetch', async () => {
const browser = await webdriver(appPort, '/')
await browser.waitForCondition('!!window.next.router.isReady')

const getCacheKeys = async () => {
return (await browser.eval('Object.keys(window.next.router.sdc)'))
.map((key) => {
// strip http://localhost:PORT
// and then strip buildId prefix
return key
.substring(key.indexOf('/_next'))
.replace(/\/_next\/data\/(.*?)\//, '/_next/data/BUILD_ID/')
})
.sort()
}

const cacheKeys = await getCacheKeys()
expect(cacheKeys).toEqual(
process.env.__MIDDLEWARE_TEST
? [
'/_next/data/BUILD_ID/[name].json?another=value&name=%5Bname%5D',
'/_next/data/BUILD_ID/added-later/first.json?name=added-later&comment=first',
'/_next/data/BUILD_ID/blog/321/comment/123.json?name=321&id=123',
'/_next/data/BUILD_ID/d/dynamic-1.json?id=dynamic-1',
'/_next/data/BUILD_ID/index.json',
'/_next/data/BUILD_ID/on-mount/test-w-hash.json?post=test-w-hash',
'/_next/data/BUILD_ID/p1/p2/all-ssg/hello.json?rest=hello',
'/_next/data/BUILD_ID/p1/p2/all-ssg/hello1/hello2.json?rest=hello1&rest=hello2',
'/_next/data/BUILD_ID/p1/p2/all-ssr/:42.json?rest=%3A42',
'/_next/data/BUILD_ID/p1/p2/all-ssr/hello.json?rest=hello',
'/_next/data/BUILD_ID/p1/p2/all-ssr/hello1%2F/he%2Fllo2.json?rest=hello1%2F&rest=he%2Fllo2',
'/_next/data/BUILD_ID/p1/p2/all-ssr/hello1/hello2.json?rest=hello1&rest=hello2',
'/_next/data/BUILD_ID/p1/p2/nested-all-ssg/hello.json?rest=hello',
'/_next/data/BUILD_ID/p1/p2/nested-all-ssg/hello1/hello2.json?rest=hello1&rest=hello2',
'/_next/data/BUILD_ID/post-1.json?fromHome=true&name=post-1',
'/_next/data/BUILD_ID/post-1.json?hidden=value&name=post-1',
'/_next/data/BUILD_ID/post-1.json?name=post-1',
'/_next/data/BUILD_ID/post-1.json?name=post-1&another=value',
'/_next/data/BUILD_ID/post-1/comment-1.json?name=post-1&comment=comment-1',
'/_next/data/BUILD_ID/post-1/comments.json?name=post-1',
]
: [
'/_next/data/BUILD_ID/p1/p2/all-ssg/hello.json?rest=hello',
'/_next/data/BUILD_ID/p1/p2/all-ssg/hello1/hello2.json?rest=hello1&rest=hello2',
'/_next/data/BUILD_ID/p1/p2/nested-all-ssg/hello.json?rest=hello',
'/_next/data/BUILD_ID/p1/p2/nested-all-ssg/hello1/hello2.json?rest=hello1&rest=hello2',
]
)

// ensure no new cache entries after navigation
const links = [
{
linkSelector: '#ssg-catch-all-single',
waitForSelector: '#all-ssg-content',
},
{
linkSelector: '#ssg-catch-all-single-interpolated',
waitForSelector: '#all-ssg-content',
},
{
linkSelector: '#ssg-catch-all-multi',
waitForSelector: '#all-ssg-content',
},
{
linkSelector: '#ssg-catch-all-multi-no-as',
waitForSelector: '#all-ssg-content',
},
{
linkSelector: '#ssg-catch-all-multi',
waitForSelector: '#all-ssg-content',
},
{
linkSelector: '#nested-ssg-catch-all-single',
waitForSelector: '#nested-all-ssg-content',
},
{
linkSelector: '#nested-ssg-catch-all-multi',
waitForSelector: '#nested-all-ssg-content',
},
]

for (const { linkSelector, waitForSelector } of links) {
await browser.elementByCss(linkSelector).click()
await browser.waitForElementByCss(waitForSelector)
await browser.back()
await browser.waitForElementByCss(linkSelector)
}
const newCacheKeys = await getCacheKeys()
expect(newCacheKeys).toEqual(cacheKeys)
})
}

if (dev) {
it('should not have error after pinging WebSocket', async () => {
const browser = await webdriver(appPort, '/')
Expand Down

0 comments on commit 7fd4081

Please sign in to comment.