mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
fix(cache-tags): add /index to implicit tags and adjust revalidatePath() (#84586)
This fixes an issue where the tag was `/` but the build output was `/index` (and vice-versa) meaning you couldn't use `revalidatePath()` in some cases because the tag didn't match. --------- Co-authored-by: JJ Kasper <[email protected]>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import type { OpaqueFallbackRouteParams } from '../request/fallback-params'
|
||||
import { getImplicitTags } from './implicit-tags'
|
||||
|
||||
describe('getImplicitTags()', () => {
|
||||
it.each<{
|
||||
page: string
|
||||
url: { pathname: string; search: string }
|
||||
fallbackRouteParams: null | OpaqueFallbackRouteParams
|
||||
expectedTags: string[]
|
||||
}>([
|
||||
{
|
||||
page: '/',
|
||||
url: { pathname: '/', search: '' },
|
||||
fallbackRouteParams: null,
|
||||
expectedTags: ['_N_T_/layout', '_N_T_/', '_N_T_/index'],
|
||||
},
|
||||
{
|
||||
page: '',
|
||||
url: { pathname: '/', search: '' },
|
||||
fallbackRouteParams: null,
|
||||
expectedTags: ['_N_T_/layout', '_N_T_/', '_N_T_/index'],
|
||||
},
|
||||
{
|
||||
page: '/',
|
||||
url: { pathname: '', search: '' },
|
||||
fallbackRouteParams: null,
|
||||
expectedTags: ['_N_T_/layout'],
|
||||
},
|
||||
{
|
||||
page: '/page',
|
||||
url: { pathname: '', search: '' },
|
||||
fallbackRouteParams: null,
|
||||
expectedTags: ['_N_T_/layout', '_N_T_/page'],
|
||||
},
|
||||
{
|
||||
page: '/page',
|
||||
url: { pathname: '/', search: '' },
|
||||
fallbackRouteParams: null,
|
||||
expectedTags: ['_N_T_/layout', '_N_T_/page', '_N_T_/', '_N_T_/index'],
|
||||
},
|
||||
{
|
||||
page: '/page',
|
||||
url: { pathname: '/page', search: '' },
|
||||
fallbackRouteParams: null,
|
||||
expectedTags: ['_N_T_/layout', '_N_T_/page'],
|
||||
},
|
||||
{
|
||||
page: '/index',
|
||||
url: { pathname: '/', search: '' },
|
||||
fallbackRouteParams: null,
|
||||
expectedTags: [
|
||||
'_N_T_/layout',
|
||||
'_N_T_/index/layout',
|
||||
'_N_T_/',
|
||||
'_N_T_/index',
|
||||
],
|
||||
},
|
||||
{
|
||||
page: '/hello',
|
||||
url: { pathname: '/hello', search: '' },
|
||||
fallbackRouteParams: null,
|
||||
expectedTags: ['_N_T_/layout', '_N_T_/hello/layout', '_N_T_/hello'],
|
||||
},
|
||||
{
|
||||
page: '/foo/bar/baz',
|
||||
url: { pathname: '/foo/bar/baz', search: '' },
|
||||
fallbackRouteParams: null,
|
||||
expectedTags: [
|
||||
'_N_T_/layout',
|
||||
'_N_T_/foo/layout',
|
||||
'_N_T_/foo/bar/layout',
|
||||
'_N_T_/foo/bar/baz/layout',
|
||||
'_N_T_/foo/bar/baz',
|
||||
],
|
||||
},
|
||||
])(
|
||||
'for page $page with url $url and $fallback',
|
||||
async ({ page, url, fallbackRouteParams, expectedTags }) => {
|
||||
const result = await getImplicitTags(page, url, fallbackRouteParams)
|
||||
expect(result.tags).toEqual(expectedTags)
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -79,13 +79,13 @@ export async function getImplicitTags(
|
||||
},
|
||||
fallbackRouteParams: null | OpaqueFallbackRouteParams
|
||||
): Promise<ImplicitTags> {
|
||||
const tags: string[] = []
|
||||
const tags = new Set<string>()
|
||||
|
||||
// Add the derived tags from the page.
|
||||
const derivedTags = getDerivedTags(page)
|
||||
for (let tag of derivedTags) {
|
||||
tag = `${NEXT_CACHE_IMPLICIT_TAG_ID}${tag}`
|
||||
tags.push(tag)
|
||||
tags.add(tag)
|
||||
}
|
||||
|
||||
// Add the tags from the pathname. If the route has unknown params, we don't
|
||||
@@ -95,11 +95,20 @@ export async function getImplicitTags(
|
||||
(!fallbackRouteParams || fallbackRouteParams.size === 0)
|
||||
) {
|
||||
const tag = `${NEXT_CACHE_IMPLICIT_TAG_ID}${url.pathname}`
|
||||
tags.push(tag)
|
||||
tags.add(tag)
|
||||
}
|
||||
|
||||
if (tags.has(`${NEXT_CACHE_IMPLICIT_TAG_ID}/`)) {
|
||||
tags.add(`${NEXT_CACHE_IMPLICIT_TAG_ID}/index`)
|
||||
}
|
||||
|
||||
if (tags.has(`${NEXT_CACHE_IMPLICIT_TAG_ID}/index`)) {
|
||||
tags.add(`${NEXT_CACHE_IMPLICIT_TAG_ID}/`)
|
||||
}
|
||||
|
||||
const tagsArray = Array.from(tags)
|
||||
return {
|
||||
tags,
|
||||
expirationsByCacheKind: createTagsExpirationsByCacheKind(tags),
|
||||
tags: tagsArray,
|
||||
expirationsByCacheKind: createTagsExpirationsByCacheKind(tagsArray),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export function unstable_expirePath(
|
||||
return
|
||||
}
|
||||
|
||||
let normalizedPath = `${NEXT_CACHE_IMPLICIT_TAG_ID}${originalPath}`
|
||||
let normalizedPath = `${NEXT_CACHE_IMPLICIT_TAG_ID}${originalPath || '/'}`
|
||||
|
||||
if (type) {
|
||||
normalizedPath += `${normalizedPath.endsWith('/') ? '' : '/'}${type}`
|
||||
@@ -46,7 +46,13 @@ export function unstable_expirePath(
|
||||
`Warning: a dynamic page path "${originalPath}" was passed to "expirePath", but the "type" parameter is missing. This has no effect by default, see more info here https://nextjs.org/docs/app/api-reference/functions/unstable_expirePath`
|
||||
)
|
||||
}
|
||||
return revalidate([normalizedPath], `unstable_expirePath ${originalPath}`)
|
||||
const tags = [normalizedPath]
|
||||
if (normalizedPath === `${NEXT_CACHE_IMPLICIT_TAG_ID}/`) {
|
||||
tags.push(`${NEXT_CACHE_IMPLICIT_TAG_ID}/index`)
|
||||
} else if (normalizedPath === `${NEXT_CACHE_IMPLICIT_TAG_ID}/index`) {
|
||||
tags.push(`${NEXT_CACHE_IMPLICIT_TAG_ID}/`)
|
||||
}
|
||||
return revalidate(tags, `unstable_expirePath ${originalPath}`)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,7 +77,7 @@ export function revalidatePath(originalPath: string, type?: 'layout' | 'page') {
|
||||
return
|
||||
}
|
||||
|
||||
let normalizedPath = `${NEXT_CACHE_IMPLICIT_TAG_ID}${originalPath}`
|
||||
let normalizedPath = `${NEXT_CACHE_IMPLICIT_TAG_ID}${originalPath || '/'}`
|
||||
|
||||
if (type) {
|
||||
normalizedPath += `${normalizedPath.endsWith('/') ? '' : '/'}${type}`
|
||||
@@ -80,7 +86,15 @@ export function revalidatePath(originalPath: string, type?: 'layout' | 'page') {
|
||||
`Warning: a dynamic page path "${originalPath}" was passed to "revalidatePath", but the "type" parameter is missing. This has no effect by default, see more info here https://nextjs.org/docs/app/api-reference/functions/revalidatePath`
|
||||
)
|
||||
}
|
||||
return revalidate([normalizedPath], `revalidatePath ${originalPath}`)
|
||||
|
||||
const tags = [normalizedPath]
|
||||
if (normalizedPath === `${NEXT_CACHE_IMPLICIT_TAG_ID}/`) {
|
||||
tags.push(`${NEXT_CACHE_IMPLICIT_TAG_ID}/index`)
|
||||
} else if (normalizedPath === `${NEXT_CACHE_IMPLICIT_TAG_ID}/index`) {
|
||||
tags.push(`${NEXT_CACHE_IMPLICIT_TAG_ID}/`)
|
||||
}
|
||||
|
||||
return revalidate(tags, `revalidatePath ${originalPath}`)
|
||||
}
|
||||
|
||||
function revalidate(tags: string[], expression: string) {
|
||||
|
||||
@@ -28,7 +28,7 @@ describe('use-cache-custom-handler', () => {
|
||||
expect(cliOutput).toContain('ModernCustomCacheHandler::refreshTags')
|
||||
|
||||
expect(next.cliOutput.slice(outputIndex)).toMatch(
|
||||
/ModernCustomCacheHandler::get \["(development|[A-Za-z0-9_-]{21})","([0-9a-f]{2})+",\[\]\] \[ '_N_T_\/layout', '_N_T_\/page', '_N_T_\/' \]/
|
||||
/ModernCustomCacheHandler::get \["(development|[A-Za-z0-9_-]{21})","([0-9a-f]{2})+",\[\]\] \[ '_N_T_\/layout', '_N_T_\/page', '_N_T_\/', '_N_T_\/index' \]/
|
||||
)
|
||||
|
||||
expect(next.cliOutput.slice(outputIndex)).toMatch(
|
||||
@@ -52,7 +52,7 @@ describe('use-cache-custom-handler', () => {
|
||||
// to compare the cache entries timestamp with the expiration of the
|
||||
// implicit tags.
|
||||
expect(next.cliOutput.slice(outputIndex)).toContain(
|
||||
`ModernCustomCacheHandler::getExpiration ["_N_T_/layout","_N_T_/page","_N_T_/"]`
|
||||
`ModernCustomCacheHandler::getExpiration ["_N_T_/layout","_N_T_/page","_N_T_/","_N_T_/index"]`
|
||||
)
|
||||
|
||||
// Because we use a low `revalidate` value for the "use cache" function, new
|
||||
|
||||
Reference in New Issue
Block a user