Skip to content

Commit

Permalink
prevent revalidateTag/Path during render (#71093)
Browse files Browse the repository at this point in the history
Makes `revalidatePath` and `revalidateTag` throw if called during
render. Revalidating is a side-effecting operation so it should not be
allowed there.

x-ref:
#70642 (comment)
  • Loading branch information
lubieowoce authored Oct 11, 2024
1 parent 1b21c3a commit fd552c5
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 2 deletions.
5 changes: 5 additions & 0 deletions packages/next/src/server/web/spec-extension/revalidate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ function revalidate(tag: string, expression: string) {
`Route ${store.route} used "${expression}" inside a function cached with "unstable_cache(...)" which is unsupported. To ensure revalidation is performed consistently it must always happen outside of renders and cached functions. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering`
)
}
if (workUnitStore.phase === 'render') {
throw new Error(
`Route ${store.route} used "${expression}" during render which is unsupported. To ensure revalidation is performed consistently it must always happen outside of renders and cached functions. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering`
)
}
}

// a route that makes use of revalidation APIs should be considered dynamic
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use server'

import Link from 'next/link'
import { revalidateTag } from 'next/cache'

const RevalidateViaPage = async ({
searchParams,
}: {
searchParams: Promise<{ tag: string }>
}) => {
const { tag } = await searchParams
revalidateTag(tag)

return (
<div className="flex flex-col items-center justify-center h-screen">
<pre>Tag [{tag}] has been revalidated</pre>
<Link href="/" id="home">
To Home
</Link>
</div>
)
}

export default RevalidateViaPage
30 changes: 28 additions & 2 deletions test/e2e/app-dir/revalidatetag-rsc/revalidatetag-rsc.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { nextTestSetup } from 'e2e-utils'
import { retry } from 'next-test-utils'
import { getRedboxHeader, retry } from 'next-test-utils'

describe('revalidateTag-rsc', () => {
const { next } = nextTestSetup({
const { next, isNextDev, isNextDeploy } = nextTestSetup({
files: __dirname,
})

Expand All @@ -20,4 +20,30 @@ describe('revalidateTag-rsc', () => {
expect(randomNumber3).not.toEqual(randomNumber)
})
})

if (!isNextDeploy) {
// skipped in deploy because it uses `next.cliOutput`
it('should error if revalidateTag is called during render', async () => {
const browser = await next.browser('/')
await browser.elementByCss('#revalidate-via-page').click()

if (isNextDev) {
await retry(async () => {
expect(await getRedboxHeader(browser)).toContain(
'Route /revalidate_via_page used "revalidateTag data"'
)
})
} else {
await retry(async () => {
expect(
await browser.eval('document.documentElement.innerHTML')
).toContain('Application error: a server-side exception has occurred')
})
}

expect(next.cliOutput).toContain(
'Route /revalidate_via_page used "revalidateTag data"'
)
})
}
})

0 comments on commit fd552c5

Please sign in to comment.