Skip to content

Commit

Permalink
fix root page revalidation when redirecting in a server action (#64730)
Browse files Browse the repository at this point in the history
When a server action triggers a redirect, we're incorrectly applying a
refresh marker to the segment they were on, rather than the segment they
were being redirected to. As a result, when revalidation occurs (via
`revalidateX` or `router.refresh()`), the top-level segment would be
replaced with data for an incorrect segment.

For example, if triggering a redirect action from `/redirect` to `/`,
the router state tree would save a reference to `/redirect`. The next
time a refresh or revalidate happens, it'd refresh the segment data for
`/redirect` instead of `/`.

Fixes #64728


Closes NEXT-3156
  • Loading branch information
ztanner authored and huozhi committed Apr 23, 2024
1 parent 8b4c234 commit dd44191
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,9 @@ export function serverActionReducer(
[''],
currentTree,
treePatch,
href
redirectLocation
? createHrefFromUrl(redirectLocation)
: state.canonicalUrl
)

if (newTree === null) {
Expand Down
8 changes: 7 additions & 1 deletion test/e2e/app-dir/parallel-routes-revalidation/app/actions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'

const data = []
let data = []

export async function addData(newData: string) {
// sleep 1s
Expand All @@ -21,3 +22,8 @@ export async function redirectAction() {
await new Promise((res) => setTimeout(res, 1000))
redirect('/')
}

export async function clearData() {
data = []
revalidatePath('/')
}
5 changes: 4 additions & 1 deletion test/e2e/app-dir/parallel-routes-revalidation/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Link from 'next/link'
import { getData } from './actions'
import { clearData, getData } from './actions'

export default async function Home() {
const data = await getData()
Expand All @@ -14,6 +14,9 @@ export default async function Home() {
<Link href="/redirect-modal">Open Redirect Modal</Link>
<Link href="/detail-page">Intercepted Detail Page</Link>
<div id="random-number">{randomNumber}</div>
<form action={clearData}>
<button id="clear-entries">Clear Entries</button>
</form>
<div>
<h1>Current Data</h1>
<ul id="entries">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { redirect } from 'next/navigation'

export default function Page() {
return (
<form
action={async () => {
'use server'
redirect('/')
}}
>
<button type="submit">Redirect</button>
</form>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,29 @@ createNextDescribe(
)
})

it('should refresh the correct page when a server action triggers a redirect', async () => {
const browser = await next.browser('/redirect')
await browser.elementByCss('button').click()

await browser.elementByCss("[href='/revalidate-modal']").click()

await check(() => browser.hasElementByCssSelector('#create-entry'), true)

await browser.elementById('clear-entries').click()

await retry(async () => {
// confirm there aren't any entries yet
expect((await browser.elementsByCss('#entries li')).length).toBe(0)
})

await browser.elementById('create-entry').click()

await retry(async () => {
// we created an entry and called revalidate, so we should have 1 entry
expect((await browser.elementsByCss('#entries li')).length).toBe(1)
})
})

describe.each([
{ basePath: '/refreshing', label: 'regular', withSearchParams: false },
{ basePath: '/refreshing', label: 'regular', withSearchParams: true },
Expand Down

0 comments on commit dd44191

Please sign in to comment.