vercel/next.js · #98993
Split revalidation errors by execution context
packages/next/src/server/app-render/work-unit-async-storage.external.ts3 + / 1 −
@@ -436,10 +436,12 @@ export interface UnstableCacheStore extends CommonCacheStore { */ export type CacheStore = UseCacheStore | UnstableCacheStore +export type BuildTimeGeneratorName = 'generateStaticParams'+ export interface BuildTimeGeneratorStore extends CommonWorkUnitStore { readonly type: 'build-time-generator' /** The public export name to use in diagnostics, regardless of bundling. */- readonly functionName: string+ readonly functionName: BuildTimeGeneratorName readonly rootParams: Params } packages/next/src/server/use-cache/use-cache-messages.ts22 + / 2 −
@@ -4,6 +4,8 @@ * immediate fix, then link to the relevant docs. */ +import type { BuildTimeGeneratorName } from '../app-render/work-unit-async-storage.external'+ const NEXT_REQUEST_IN_USE_CACHE = 'https://nextjs.org/docs/messages/next-request-in-use-cache' @@ -122,12 +124,30 @@ export function createDraftModeMutationInUnstableCacheError( } export function createRevalidateDuringRenderError(+ route: string,+ expression: string+): Error {+ return new Error(+ `Route "${route}": \`${expression}\` can't be called during render. Call it from a Server Action or Route Handler instead.\nLearn more: ${REVALIDATE_IN_USE_CACHE}`+ )+}++export function createRevalidateInCachedFunctionError(+ route: string,+ expression: string+): Error {+ return new Error(+ `Route "${route}": \`${expression}\` can't be called inside a cached function. Call it from a Server Action or Route Handler instead.\nLearn more: ${REVALIDATE_IN_USE_CACHE}`+ )+}++export function createRevalidateInBuildTimeGeneratorError( route: string, expression: string,- generatorName?: string+ generatorName: BuildTimeGeneratorName ): Error { return new Error(- `Route "${route}": \`${expression}\` can't be called during render, inside a cached function, or inside \`${generatorName ?? 'generateStaticParams'}\`. Call it from a Server Action or Route Handler instead.\nLearn more: ${REVALIDATE_IN_USE_CACHE}`+ `Route "${route}": \`${expression}\` can't be called inside \`${generatorName}\`. Call it from a Server Action or Route Handler instead.\nLearn more: ${REVALIDATE_IN_USE_CACHE}` ) } packages/next/src/server/web/spec-extension/revalidate.ts8 + / 7 −
@@ -14,7 +14,11 @@ import { } from '../../../shared/lib/action-revalidation-kind' import { removeTrailingSlash } from '../../../shared/lib/router/utils/remove-trailing-slash' import { encodeHeaderSafe } from '../../lib/encode-header-safe'-import { createRevalidateDuringRenderError } from '../../use-cache/use-cache-messages'+import {+ createRevalidateDuringRenderError,+ createRevalidateInBuildTimeGeneratorError,+ createRevalidateInCachedFunctionError,+} from '../../use-cache/use-cache-messages' import { validateAndNormalizeCacheLifeProfile } from '../../use-cache/cache-life-profile' type CacheLifeConfig = {@@ -145,17 +149,18 @@ function revalidate( const workUnitStore = workUnitAsyncStorage.getStore() if (workUnitStore) {- // Keep the generator's name before applying the general render-phase check.+ // Prefer the more specific contexts over the general render-phase error. switch (workUnitStore.type) { case 'build-time-generator':- throw createRevalidateDuringRenderError(+ throw createRevalidateInBuildTimeGeneratorError( store.route, expression, workUnitStore.functionName ) case 'cache': case 'private-cache': case 'unstable-cache':+ throw createRevalidateInCachedFunctionError(store.route, expression) case 'prerender': case 'prerender-runtime': case 'prerender-client':@@ -172,10 +177,6 @@ function revalidate( } switch (workUnitStore.type) {- case 'cache':- case 'private-cache':- case 'unstable-cache':- throw createRevalidateDuringRenderError(store.route, expression) case 'prerender': case 'prerender-runtime': // cacheComponents Prerendertest/e2e/app-dir/revalidatetag-rsc/app/page.tsx3 + / 0 −
@@ -19,6 +19,9 @@ export default async function Page() { <Link href="/revalidate_via_page?tag=data" id="revalidate-via-page"> Revalidate via page </Link>+ <Link href="/revalidate_via_cache" id="revalidate-via-cache">+ Revalidate via cache+ </Link> </div> ) }test/e2e/app-dir/revalidatetag-rsc/app/revalidate_via_cache/page.tsxadded13 + / 0 −
@@ -0,0 +1,13 @@+import { revalidateTag, unstable_cache } from 'next/cache'++export const dynamic = 'force-dynamic'++const revalidateInCache = unstable_cache(async () => {+ revalidateTag('data', 'max')+})++export default async function Page() {+ await revalidateInCache()++ return <p>revalidated</p>+}test/e2e/app-dir/revalidatetag-rsc/revalidatetag-rsc.test.ts24 + / 2 −
@@ -30,7 +30,7 @@ describe('revalidateTag-rsc', () => { if (isNextDev) { await waitForRedbox(browser) await expect(getRedboxHeader(browser)).resolves.toContain(- 'Route "/revalidate_via_page": `revalidateTag("data")` can\'t be called during render, inside a cached function, or inside `generateStaticParams`.'+ 'Route "/revalidate_via_page": `revalidateTag("data")` can\'t be called during render.' ) } else { await retry(async () => {@@ -41,7 +41,29 @@ describe('revalidateTag-rsc', () => { } expect(next.cliOutput).toContain(- 'Route "/revalidate_via_page": `revalidateTag("data")` can\'t be called during render, inside a cached function, or inside `generateStaticParams`. Call it from a Server Action or Route Handler instead.\nLearn more: https://nextjs.org/docs/messages/revalidate-in-use-cache'+ 'Route "/revalidate_via_page": `revalidateTag("data")` can\'t be called during render. Call it from a Server Action or Route Handler instead.\nLearn more: https://nextjs.org/docs/messages/revalidate-in-use-cache'+ )+ })++ it('should error if revalidateTag is called inside a cached function', async () => {+ const browser = await next.browser('/')+ await browser.elementByCss('#revalidate-via-cache').click()++ if (isNextDev) {+ await waitForRedbox(browser)+ await expect(getRedboxHeader(browser)).resolves.toContain(+ 'Route "/revalidate_via_cache": `revalidateTag("data")` can\'t be called inside a cached function.'+ )+ } else {+ await retry(async () => {+ expect(+ await browser.eval('document.documentElement.innerHTML')+ ).toContain('This page couldn\u2019t load')+ })+ }++ expect(next.cliOutput).toContain(+ 'Route "/revalidate_via_cache": `revalidateTag("data")` can\'t be called inside a cached function. Call it from a Server Action or Route Handler instead.\nLearn more: https://nextjs.org/docs/messages/revalidate-in-use-cache' ) }) }test/production/app-dir/generate-static-params-errors/app/[lang]/revalidate-tag/[slug]/page.tsxadded10 + / 0 −
@@ -0,0 +1,10 @@+import { revalidateTag } from 'next/cache'++export default function Page() {+ return <p>revalidate-tag</p>+}++export async function generateStaticParams() {+ revalidateTag('data', 'max')+ return [{ slug: 'test' }]+}test/production/app-dir/generate-static-params-errors/generate-static-params-errors.test.ts7 + / 0 −
@@ -47,6 +47,13 @@ describe('generate-static-params-errors', () => { ) }) + it('should error when revalidateTag() is called inside generateStaticParams', async () => {+ await buildRoute('app/[lang]/revalidate-tag/[slug]/page.tsx')+ expect(getCliOutput()).toContain(+ 'Error: Route "/[lang]/revalidate-tag/[slug]": `revalidateTag("data")` can\'t be called inside `generateStaticParams`. Call it from a Server Action or Route Handler instead.\nLearn more: https://nextjs.org/docs/messages/revalidate-in-use-cache'+ )+ })+ it('should allow root params access inside generateStaticParams', async () => { await buildRoute('app/[lang]/root-params/[slug]/page.tsx') expect(getCliOutput()).not.toContain('Error')