diff --git a/docs/02-app/02-api-reference/04-functions/fetch.mdx b/docs/02-app/02-api-reference/04-functions/fetch.mdx index 5bea3d17f77f8..4cca0eb9400d9 100644 --- a/docs/02-app/02-api-reference/04-functions/fetch.mdx +++ b/docs/02-app/02-api-reference/04-functions/fetch.mdx @@ -98,7 +98,7 @@ Set the cache lifetime of a resource (in seconds). fetch(`https://...`, { next: { tags: ['collection'] } }) ``` -Set the cache tags of a resource. Data can then be revalidated on-demand using [`revalidateTag`](https://nextjs.org/docs/app/api-reference/functions/revalidateTag). +Set the cache tags of a resource. Data can then be revalidated on-demand using [`revalidateTag`](https://nextjs.org/docs/app/api-reference/functions/revalidateTag). The max length for a custom tag is 256 characters. ## Version History diff --git a/docs/02-app/02-api-reference/04-functions/revalidatePath.mdx b/docs/02-app/02-api-reference/04-functions/revalidatePath.mdx index abc0b6c9f6f7d..cdbb54b12db95 100644 --- a/docs/02-app/02-api-reference/04-functions/revalidatePath.mdx +++ b/docs/02-app/02-api-reference/04-functions/revalidatePath.mdx @@ -8,16 +8,16 @@ description: API Reference for the revalidatePath function. > **Good to know**: > > - `revalidatePath` is available in both [Node.js and Edge runtimes](/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes). -> - `revalidatePath` will revalidate _all segments_ under a dynamic route segment. For example, if you have a dynamic segment `/product/[id]` and you call `revalidatePath('/product/[id]')`, then all segments under `/product/[id]` will be revalidated as requested. -> - `revalidatePath` only invalidates the cache when the path is next visited. This means calling `revalidatePath` with a dynamic route segment will not immediately trigger many revalidations at once. The invalidation only happens when the path is next visited. +> - `revalidatePath` only invalidates the cache when the included path is next visited. This means calling `revalidatePath` with a dynamic route segment will not immediately trigger many revalidations at once. The invalidation only happens when the path is next visited. ## Parameters ```tsx -revalidatePath(path: string): void; +revalidatePath(path: string, type?: 'page' | 'layout'): void; ``` -- `path`: A string representing the filesystem path associated with the data you want to revalidate. This is **not** the literal route segment (e.g. `/product/123`) but instead the path on the filesystem (e.g. `/product/[id]`). +- `path`: A string representing the filesystem path associated with the data you want to revalidate. This is the literal route segment (for example, `/product/123`) not the path on the filesystem `/product/[slug]/page`. Must be less than 1024 characters. +- `type`: (optional) `'page'` or `'layout'` string to change the type of path to revalidate. ## Returns @@ -25,6 +25,33 @@ revalidatePath(path: string): void; ## Examples +### Revalidating A Specific URL + +```ts +import { revalidatePath } from 'next/cache' +revalidatePath('/blog/post-1') +``` + +This will revalidate one specific URL on the next page visit. + +### Revalidating A Page Path + +```ts +import { revalidatePath } from 'next/cache' +revalidatePath('/blog/[slug]', 'page') +``` + +This will revalidate any URL that matches the provided `page` file on the next page visit. This will _not_ invalidate pages beneath the specific page. For example, `/blog/[slug]` won't invalidate `/blog/[slug]/[author]`. + +### Revalidating A Layout Path + +```ts +import { revalidatePath } from 'next/cache' +revalidatePath('/blog/[slug]', 'layout') +``` + +This will revalidate any URL that matches the provided `layout` file on the next page visit. This will cause pages beneath with the same layout to revalidate on the next visit. For example, in the above case, `/blog/[slug]/[another]` would also revalidate on the next visit. + ### Server Action ```ts filename="app/actions.ts" switcher diff --git a/docs/02-app/02-api-reference/04-functions/revalidateTag.mdx b/docs/02-app/02-api-reference/04-functions/revalidateTag.mdx index 6d14baedcde5c..dd8680f59df3e 100644 --- a/docs/02-app/02-api-reference/04-functions/revalidateTag.mdx +++ b/docs/02-app/02-api-reference/04-functions/revalidateTag.mdx @@ -16,7 +16,7 @@ description: API Reference for the revalidateTag function. revalidateTag(tag: string): void; ``` -- `tag`: A string representing the cache tag associated with the data you want to revalidate. +- `tag`: A string representing the cache tag associated with the data you want to revalidate. Must be less than or equal to 256 characters. You can add tags to `fetch` as follows: diff --git a/packages/next/src/lib/constants.ts b/packages/next/src/lib/constants.ts index 46f85311a9f0a..65370408b0bca 100644 --- a/packages/next/src/lib/constants.ts +++ b/packages/next/src/lib/constants.ts @@ -12,6 +12,8 @@ export const NEXT_CACHE_REVALIDATED_TAGS_HEADER = 'x-next-revalidated-tags' export const NEXT_CACHE_REVALIDATE_TAG_TOKEN_HEADER = 'x-next-revalidate-tag-token' +export const NEXT_CACHE_TAG_MAX_LENGTH = 256 +export const NEXT_CACHE_SOFT_TAG_MAX_LENGTH = 1024 export const NEXT_CACHE_IMPLICIT_TAG_ID = '_N_T_' // in seconds diff --git a/packages/next/src/server/lib/patch-fetch.ts b/packages/next/src/server/lib/patch-fetch.ts index 080361704b718..7f250860f51fe 100644 --- a/packages/next/src/server/lib/patch-fetch.ts +++ b/packages/next/src/server/lib/patch-fetch.ts @@ -3,11 +3,45 @@ import type * as ServerHooks from '../../client/components/hooks-server-context' import { AppRenderSpan, NextNodeServerSpan } from './trace/constants' import { getTracer, SpanKind } from './trace/tracer' -import { CACHE_ONE_YEAR, NEXT_CACHE_IMPLICIT_TAG_ID } from '../../lib/constants' +import { + CACHE_ONE_YEAR, + NEXT_CACHE_IMPLICIT_TAG_ID, + NEXT_CACHE_TAG_MAX_LENGTH, +} from '../../lib/constants' import * as Log from '../../build/output/log' const isEdgeRuntime = process.env.NEXT_RUNTIME === 'edge' +export function validateTags(tags: any[], description: string) { + const validTags: string[] = [] + const invalidTags: Array<{ + tag: any + reason: string + }> = [] + + for (const tag of tags) { + if (typeof tag !== 'string') { + invalidTags.push({ tag, reason: 'invalid type, must be a string' }) + } else if (tag.length > NEXT_CACHE_TAG_MAX_LENGTH) { + invalidTags.push({ + tag, + reason: `exceeded max length of ${NEXT_CACHE_TAG_MAX_LENGTH}`, + }) + } else { + validTags.push(tag) + } + } + + if (invalidTags.length > 0) { + console.warn(`Warning: invalid tags passed to ${description}: `) + + for (const { tag, reason } of invalidTags) { + console.log(`tag: "${tag}" ${reason}`) + } + } + return validTags +} + const getDerivedTags = (pathname: string): string[] => { const derivedTags: string[] = [`/layout`] @@ -194,7 +228,10 @@ export function patchFetch({ // RequestInit doesn't keep extra fields e.g. next so it's // only available if init is used separate let curRevalidate = getNextField('revalidate') - const tags: string[] = getNextField('tags') || [] + const tags: string[] = validateTags( + getNextField('tags') || [], + `fetch ${input.toString()}` + ) if (Array.isArray(tags)) { if (!staticGenerationStore.tags) { diff --git a/packages/next/src/server/web/spec-extension/revalidate-path.ts b/packages/next/src/server/web/spec-extension/revalidate-path.ts index 8629a14b4994a..c8c7c6e2fbce1 100644 --- a/packages/next/src/server/web/spec-extension/revalidate-path.ts +++ b/packages/next/src/server/web/spec-extension/revalidate-path.ts @@ -1,11 +1,26 @@ import { revalidateTag } from './revalidate-tag' -import { NEXT_CACHE_IMPLICIT_TAG_ID } from '../../../lib/constants' +import { isDynamicRoute } from '../../../shared/lib/router/utils' +import { + NEXT_CACHE_IMPLICIT_TAG_ID, + NEXT_CACHE_SOFT_TAG_MAX_LENGTH, +} from '../../../lib/constants' -export function revalidatePath(path: string, type?: 'layout' | 'page') { - path = `${NEXT_CACHE_IMPLICIT_TAG_ID}${path}` +export function revalidatePath(originalPath: string, type?: 'layout' | 'page') { + if (originalPath.length > NEXT_CACHE_SOFT_TAG_MAX_LENGTH) { + console.warn( + `Warning: revalidatePath received "${originalPath}" which exceeded max length of ${NEXT_CACHE_SOFT_TAG_MAX_LENGTH}. See more info here https://nextjs.org/docs/app/api-reference/functions/revalidatePath` + ) + return + } + + let normalizedPath = `${NEXT_CACHE_IMPLICIT_TAG_ID}${originalPath}` if (type) { - path += `${path.endsWith('/') ? '' : '/'}${type}` + normalizedPath += `${normalizedPath.endsWith('/') ? '' : '/'}${type}` + } else if (isDynamicRoute(originalPath)) { + console.warn( + `Warning: a dynamic page path "${originalPath}" was passed to "revalidatePath" without the "page" argument. This has no affect by default, see more info here https://nextjs.org/docs/app/api-reference/functions/revalidatePath` + ) } - return revalidateTag(path) + return revalidateTag(normalizedPath) } diff --git a/packages/next/src/server/web/spec-extension/unstable-cache.ts b/packages/next/src/server/web/spec-extension/unstable-cache.ts index 1e85b5c290971..34dcab8158e0a 100644 --- a/packages/next/src/server/web/spec-extension/unstable-cache.ts +++ b/packages/next/src/server/web/spec-extension/unstable-cache.ts @@ -4,7 +4,7 @@ import { StaticGenerationAsyncStorage, } from '../../../client/components/static-generation-async-storage.external' import { CACHE_ONE_YEAR } from '../../../lib/constants' -import { addImplicitTags } from '../../lib/patch-fetch' +import { addImplicitTags, validateTags } from '../../lib/patch-fetch' type Callback = (...args: any[]) => Promise @@ -56,7 +56,10 @@ export function unstable_cache( isStaticGeneration: !!store?.isStaticGeneration, }, async () => { - const tags = options.tags || [] + const tags = validateTags( + options.tags || [], + `unstable_cache ${cb.toString()}` + ) if (Array.isArray(tags) && store) { if (!store.tags) {