diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 740444cd7cbfb..37160306a8b2f 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -512,6 +512,22 @@ async function generateDynamicFlightRenderResult( onError, } ) + await waitAtLeastOneReactRenderTask() + + if ( + ctx.staticGenerationStore.pendingRevalidates || + ctx.staticGenerationStore.revalidatedTags || + ctx.staticGenerationStore.pendingRevalidateWrites + ) { + const promises = Promise.all([ + ctx.staticGenerationStore.incrementalCache?.revalidateTag( + ctx.staticGenerationStore.revalidatedTags || [] + ), + ...Object.values(ctx.staticGenerationStore.pendingRevalidates || {}), + ...(ctx.staticGenerationStore.pendingRevalidateWrites || []), + ]) + ctx.renderOpts.waitUntil = (p) => promises.then(() => p) + } return new FlightRenderResult(flightReadableStream, { fetchMetrics: ctx.staticGenerationStore.fetchMetrics, @@ -1084,7 +1100,8 @@ async function renderToHTMLOrFlightImpl( // If we have pending revalidates, wait until they are all resolved. if ( staticGenerationStore.pendingRevalidates || - staticGenerationStore.pendingRevalidateWrites + staticGenerationStore.pendingRevalidateWrites || + staticGenerationStore.revalidatedTags ) { options.waitUntil = Promise.all([ staticGenerationStore.incrementalCache?.revalidateTag( @@ -1193,7 +1210,8 @@ async function renderToHTMLOrFlightImpl( // If we have pending revalidates, wait until they are all resolved. if ( staticGenerationStore.pendingRevalidates || - staticGenerationStore.pendingRevalidateWrites + staticGenerationStore.pendingRevalidateWrites || + staticGenerationStore.revalidatedTags ) { options.waitUntil = Promise.all([ staticGenerationStore.incrementalCache?.revalidateTag( diff --git a/test/e2e/app-dir/revalidatetag-rsc/app/RevalidateViaForm.tsx b/test/e2e/app-dir/revalidatetag-rsc/app/RevalidateViaForm.tsx new file mode 100644 index 0000000000000..762d46859ca11 --- /dev/null +++ b/test/e2e/app-dir/revalidatetag-rsc/app/RevalidateViaForm.tsx @@ -0,0 +1,17 @@ +'use client' + +import { revalidate } from './actions/revalidate' + +export default function RevalidateViaForm({ tag }: { tag: string }) { + const handleRevalidate = async () => { + await revalidate(tag) + } + + return ( +
+ +
+ ) +} diff --git a/test/e2e/app-dir/revalidatetag-rsc/app/actions/revalidate.ts b/test/e2e/app-dir/revalidatetag-rsc/app/actions/revalidate.ts new file mode 100644 index 0000000000000..2fba1ff18f207 --- /dev/null +++ b/test/e2e/app-dir/revalidatetag-rsc/app/actions/revalidate.ts @@ -0,0 +1,11 @@ +'use server' + +import { revalidateTag } from 'next/cache' + +export const revalidate = async ( + tag: string +): Promise<{ revalidated: boolean }> => { + revalidateTag(tag) + + return { revalidated: true } +} diff --git a/test/e2e/app-dir/revalidatetag-rsc/app/layout.tsx b/test/e2e/app-dir/revalidatetag-rsc/app/layout.tsx new file mode 100644 index 0000000000000..716a8db36f52c --- /dev/null +++ b/test/e2e/app-dir/revalidatetag-rsc/app/layout.tsx @@ -0,0 +1,9 @@ +import { ReactNode } from 'react' + +export default function Root({ children }: { children: ReactNode }) { + return ( + + {children} + + ) +} diff --git a/test/e2e/app-dir/revalidatetag-rsc/app/page.tsx b/test/e2e/app-dir/revalidatetag-rsc/app/page.tsx new file mode 100644 index 0000000000000..e4ced2724de32 --- /dev/null +++ b/test/e2e/app-dir/revalidatetag-rsc/app/page.tsx @@ -0,0 +1,24 @@ +import RevalidateViaForm from './RevalidateViaForm' +import Link from 'next/link' + +export default async function Page() { + const data = await fetch( + 'https://next-data-api-endpoint.vercel.app/api/random', + { + next: { + tags: ['data'], + revalidate: false, + }, + } + ).then((res) => res.text()) + + return ( +
+ {data} + + + Revalidate via page + +
+ ) +} diff --git a/test/e2e/app-dir/revalidatetag-rsc/app/revalidate_via_page/page.tsx b/test/e2e/app-dir/revalidatetag-rsc/app/revalidate_via_page/page.tsx new file mode 100644 index 0000000000000..319a31582f655 --- /dev/null +++ b/test/e2e/app-dir/revalidatetag-rsc/app/revalidate_via_page/page.tsx @@ -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 ( +
+
Tag [{tag}] has been revalidated
+ + To Home + +
+ ) +} + +export default RevalidateViaPage diff --git a/test/e2e/app-dir/revalidatetag-rsc/next.config.js b/test/e2e/app-dir/revalidatetag-rsc/next.config.js new file mode 100644 index 0000000000000..807126e4cf0bf --- /dev/null +++ b/test/e2e/app-dir/revalidatetag-rsc/next.config.js @@ -0,0 +1,6 @@ +/** + * @type {import('next').NextConfig} + */ +const nextConfig = {} + +module.exports = nextConfig diff --git a/test/e2e/app-dir/revalidatetag-rsc/revalidatetag-rsc.test.ts b/test/e2e/app-dir/revalidatetag-rsc/revalidatetag-rsc.test.ts new file mode 100644 index 0000000000000..5355fb7d1c602 --- /dev/null +++ b/test/e2e/app-dir/revalidatetag-rsc/revalidatetag-rsc.test.ts @@ -0,0 +1,38 @@ +import { nextTestSetup } from 'e2e-utils' +import { retry } from 'next-test-utils' + +describe('revalidateTag-rsc', () => { + const { next } = nextTestSetup({ + files: __dirname, + }) + + it('should revalidate fetch cache if revalidateTag invoked via server action', async () => { + const browser = await next.browser('/') + const randomNumber = await browser.elementById('data').text() + await browser.refresh() + const randomNumber2 = await browser.elementById('data').text() + expect(randomNumber).toEqual(randomNumber2) + + await browser.elementByCss('#submit-form').click() + + await retry(async () => { + const randomNumber3 = await browser.elementById('data').text() + expect(randomNumber3).not.toEqual(randomNumber) + }) + }) + + it('should revalidate fetch cache if revalidateTag invoked via server component', async () => { + const browser = await next.browser('/') + const randomNumber = await browser.elementById('data').text() + await browser.refresh() + const randomNumber2 = await browser.elementById('data').text() + expect(randomNumber).toEqual(randomNumber2) + + await browser.elementByCss('#revalidate-via-page').click() + await browser.waitForElementByCss('#home') + await browser.elementByCss('#home').click() + await browser.waitForElementByCss('#data') + const randomNumber3 = await browser.elementById('data').text() + expect(randomNumber3).not.toEqual(randomNumber) + }) +})