Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix revalidateTag() behaviour when invoked in server components #70446

Merged
merged 10 commits into from
Sep 30, 2024
22 changes: 20 additions & 2 deletions packages/next/src/server/app-render/app-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down
17 changes: 17 additions & 0 deletions test/e2e/app-dir/revalidatetag-rsc/app/RevalidateViaForm.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<form action={handleRevalidate}>
<button type="submit" id="submit-form" className="underline">
Revalidate via form
</button>
</form>
)
}
11 changes: 11 additions & 0 deletions test/e2e/app-dir/revalidatetag-rsc/app/actions/revalidate.ts
Original file line number Diff line number Diff line change
@@ -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 }
}
9 changes: 9 additions & 0 deletions test/e2e/app-dir/revalidatetag-rsc/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ReactNode } from 'react'

export default function Root({ children }: { children: ReactNode }) {
return (
<html>
<body>{children}</body>
</html>
)
}
24 changes: 24 additions & 0 deletions test/e2e/app-dir/revalidatetag-rsc/app/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<span id="data">{data}</span>
<RevalidateViaForm tag="data" />
<Link href="/revalidate_via_page?tag=data" id="revalidate-via-page">
Revalidate via page
</Link>
</div>
)
}
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
6 changes: 6 additions & 0 deletions test/e2e/app-dir/revalidatetag-rsc/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {}

module.exports = nextConfig
38 changes: 38 additions & 0 deletions test/e2e/app-dir/revalidatetag-rsc/revalidatetag-rsc.test.ts
Original file line number Diff line number Diff line change
@@ -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)
})
})
Loading