-
Notifications
You must be signed in to change notification settings - Fork 27.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix parallel routes with server actions / revalidating router cache (#…
…59585) ### What? There are a bunch of different bugs caused by the same underlying issue, but the common thread is that performing any sort of router cache update (either through `router.refresh()`, `revalidatePath()`, or `redirect()`) inside of a parallel route would break the router preventing subsequent actions, and not resolve any pending state such as from `useFormState`. ### Why? `applyPatch` is responsible for taking an update response from the server and merging it into the client router cache. However, there's specific bailout logic to skip over applying the patch to a `__DEFAULT__` segment (which corresponds with a `default.tsx` page). When the router detects a cache node that is expected to be rendered on the page but contains no data, the router will trigger a lazy fetch to retrieve the data that's expected to be there ([ref](https://github.com/vercel/next.js/blob/5adacb69126e0fd7dff7ebd45278c0dfd42f6116/packages/next/src/client/components/layout-router.tsx#L359-L370)) and then update the router cache once the data resolves ([ref](https://github.com/vercel/next.js/blob/5adacb69126e0fd7dff7ebd45278c0dfd42f6116/packages/next/src/client/components/layout-router.tsx#L399-L404)). This is causing the router to get stuck in a loop: it'll fetch the data for the cache node, send the data to the router reducer to merge it into the existing cache nodes, skip merging that data in for `__DEFAULT__` segments, and repeat. ### How? We currently assign `__DEFAULT__` to have `notFound()` behavior when there isn't a `default.tsx` component for a particular segment. This makes it so that when loading a page that renders a slot without slot content / a `default`, it 404s. But when performing a client-side navigation, the intended behavior is different: we keep whatever was in the `default` slots place, until the user refreshes the page, which would then 404. However, this logic is incorrect when triggering any of the above mentioned cache node revalidation strategies: if we always skip applying to the `__DEFAULT__` segment, slots will never properly handle reducer actions that rely on making changes to their cache nodes. This splits these different `applyPatch` functions: one that will apply to the full tree, and another that'll apply to everything except the default segments with the existing bailout condition. Fixes #54173 Fixes #58772 Fixes #54723 Fixes #57665 Closes NEXT-1706 Closes NEXT-1815 Closes NEXT-1812
- Loading branch information
Showing
22 changed files
with
347 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 3 additions & 0 deletions
3
test/e2e/app-dir/parallel-routes-revalidation/app/@dialog/default.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export default function Default() { | ||
return null | ||
} |
3 changes: 3 additions & 0 deletions
3
test/e2e/app-dir/parallel-routes-revalidation/app/@dialog/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export default function Page() { | ||
return null | ||
} |
21 changes: 21 additions & 0 deletions
21
test/e2e/app-dir/parallel-routes-revalidation/app/@dialog/refresh-modal/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
'use client' | ||
import Link from 'next/link' | ||
import { useRouter } from 'next/navigation' | ||
import React from 'react' | ||
|
||
export default function Page() { | ||
const router = useRouter() | ||
|
||
return ( | ||
<dialog open> | ||
<h1>Modal</h1> | ||
|
||
<br /> | ||
|
||
<button onClick={() => router.refresh()} id="refresh-router"> | ||
Refresh Router | ||
</button> | ||
<Link href="/">Close</Link> | ||
</dialog> | ||
) | ||
} |
30 changes: 30 additions & 0 deletions
30
test/e2e/app-dir/parallel-routes-revalidation/app/@dialog/revalidate-modal/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import Link from 'next/link' | ||
|
||
import { revalidatePath } from 'next/cache' | ||
import { addData } from '../../actions' | ||
|
||
export default function Page() { | ||
async function createItem() { | ||
'use server' | ||
|
||
await addData(new Date().toISOString()) | ||
|
||
revalidatePath('/', 'layout') | ||
} | ||
|
||
return ( | ||
<dialog open> | ||
<h1>Modal</h1> | ||
|
||
<br /> | ||
|
||
<form action={createItem}> | ||
<button type="submit" className="button" id="create-entry"> | ||
Create New Item | ||
</button> | ||
</form> | ||
|
||
<Link href="/">Close</Link> | ||
</dialog> | ||
) | ||
} |
14 changes: 14 additions & 0 deletions
14
test/e2e/app-dir/parallel-routes-revalidation/app/@interception/(.)redirect-modal/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { RedirectForm } from '../../components/RedirectForm' | ||
import { redirectAction } from '../../actions' | ||
|
||
export default function Page() { | ||
return ( | ||
<dialog open> | ||
<h1>Modal</h1> | ||
|
||
<br /> | ||
|
||
<RedirectForm action={redirectAction} /> | ||
</dialog> | ||
) | ||
} |
3 changes: 3 additions & 0 deletions
3
test/e2e/app-dir/parallel-routes-revalidation/app/@interception/default.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export default function Default() { | ||
return null | ||
} |
23 changes: 23 additions & 0 deletions
23
test/e2e/app-dir/parallel-routes-revalidation/app/actions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
'use server' | ||
import { redirect } from 'next/navigation' | ||
|
||
const data = [] | ||
|
||
export async function addData(newData: string) { | ||
// sleep 1s | ||
await new Promise((resolve) => setTimeout(resolve, 1000)) | ||
data.push(newData) | ||
} | ||
|
||
export async function getData() { | ||
// sleep 1s | ||
await new Promise((resolve) => setTimeout(resolve, 1000)) | ||
return data | ||
} | ||
|
||
export async function redirectAction() { | ||
'use server' | ||
console.log('redirecting...') | ||
await new Promise((res) => setTimeout(res, 1000)) | ||
redirect('/') | ||
} |
11 changes: 11 additions & 0 deletions
11
test/e2e/app-dir/parallel-routes-revalidation/app/components/RedirectForm.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
'use client' | ||
|
||
export function RedirectForm({ action }: { action: () => Promise<void> }) { | ||
return ( | ||
<form action={action}> | ||
<button type="submit" className="button" id="redirect"> | ||
Redirect to Home | ||
</button> | ||
</form> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from './page' |
Oops, something went wrong.