Skip to content

Commit

Permalink
Update docs for revalidatePath fix (#55083)
Browse files Browse the repository at this point in the history
This updates docs for the fixes landed in
#53321 related to
`revalidatePath`.

Fixes: #49387

---------

Co-authored-by: Lee Robinson <me@leerob.io>
  • Loading branch information
ijjk and leerob committed Sep 12, 2023
1 parent 45fbd4d commit c2f5876
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 15 deletions.
2 changes: 1 addition & 1 deletion docs/02-app/02-api-reference/04-functions/fetch.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
35 changes: 31 additions & 4 deletions docs/02-app/02-api-reference/04-functions/revalidatePath.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,50 @@ 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

`revalidatePath` does not return any value.

## 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
2 changes: 2 additions & 0 deletions packages/next/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 39 additions & 2 deletions packages/next/src/server/lib/patch-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`]

Expand Down Expand Up @@ -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) {
Expand Down
25 changes: 20 additions & 5 deletions packages/next/src/server/web/spec-extension/revalidate-path.ts
Original file line number Diff line number Diff line change
@@ -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)
}
7 changes: 5 additions & 2 deletions packages/next/src/server/web/spec-extension/unstable-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<any>

Expand Down Expand Up @@ -56,7 +56,10 @@ export function unstable_cache<T extends Callback>(
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) {
Expand Down

0 comments on commit c2f5876

Please sign in to comment.