diff --git a/packages/next/src/client/components/app-router.tsx b/packages/next/src/client/components/app-router.tsx
index a8de967825e16..51c749f002b3a 100644
--- a/packages/next/src/client/components/app-router.tsx
+++ b/packages/next/src/client/components/app-router.tsx
@@ -186,7 +186,7 @@ function useNavigate(dispatch: React.Dispatch): RouterNavigate {
return useCallback(
(href, navigateType, forceOptimisticNavigation, shouldScroll) => {
const url = new URL(addBasePath(href), location.href)
- globalMutable.pendingNavigatePath = href
+ globalMutable.pendingNavigatePath = createHrefFromUrl(url)
return dispatch({
type: ACTION_NAVIGATE,
diff --git a/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts
index d719b84640541..d8c67e507cc95 100644
--- a/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts
+++ b/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts
@@ -163,6 +163,7 @@ export function serverActionReducer(
// unblock if a navigation event comes through
// while we've suspended on an action
if (
+ mutable.inFlightServerAction.status !== 'fulfilled' &&
mutable.globalMutable.pendingNavigatePath &&
mutable.globalMutable.pendingNavigatePath !== href
) {
diff --git a/test/e2e/app-dir/actions-navigation/app/action-after-redirect/actions.js b/test/e2e/app-dir/actions-navigation/app/action-after-redirect/actions.js
new file mode 100644
index 0000000000000..6b6be9a63af6e
--- /dev/null
+++ b/test/e2e/app-dir/actions-navigation/app/action-after-redirect/actions.js
@@ -0,0 +1,9 @@
+'use server'
+
+export async function expensiveCalculation() {
+ console.log('server action invoked')
+ // sleep for 1 second
+ await new Promise((resolve) => setTimeout(resolve, 1000))
+
+ return Math.random()
+}
diff --git a/test/e2e/app-dir/actions-navigation/app/action-after-redirect/form.js b/test/e2e/app-dir/actions-navigation/app/action-after-redirect/form.js
new file mode 100644
index 0000000000000..665cc690113e9
--- /dev/null
+++ b/test/e2e/app-dir/actions-navigation/app/action-after-redirect/form.js
@@ -0,0 +1,35 @@
+'use client'
+
+import React from 'react'
+import { expensiveCalculation } from './actions'
+
+export function Form({ randomNum }) {
+ const [isPending, setIsPending] = React.useState(false)
+ const [result, setResult] = React.useState(null)
+
+ async function handleSubmit(event) {
+ event.preventDefault()
+
+ setIsPending(true)
+ const result = await expensiveCalculation()
+ setIsPending(false)
+ setResult(result)
+ }
+
+ return (
+
+ )
+}
diff --git a/test/e2e/app-dir/actions-navigation/app/action-after-redirect/page.js b/test/e2e/app-dir/actions-navigation/app/action-after-redirect/page.js
new file mode 100644
index 0000000000000..28ea3700aa38c
--- /dev/null
+++ b/test/e2e/app-dir/actions-navigation/app/action-after-redirect/page.js
@@ -0,0 +1,11 @@
+import { Form } from './form'
+
+export default async function Page() {
+ const randomNum = Math.random()
+
+ return (
+
+
+
+ )
+}
diff --git a/test/e2e/app-dir/actions-navigation/app/layout.js b/test/e2e/app-dir/actions-navigation/app/layout.js
new file mode 100644
index 0000000000000..6d7e1ed585862
--- /dev/null
+++ b/test/e2e/app-dir/actions-navigation/app/layout.js
@@ -0,0 +1,8 @@
+export default function RootLayout({ children }) {
+ return (
+
+
+
{children}
+
+ )
+}
diff --git a/test/e2e/app-dir/actions-navigation/app/middleware-redirect/page.js b/test/e2e/app-dir/actions-navigation/app/middleware-redirect/page.js
new file mode 100644
index 0000000000000..0bd21c5bac776
--- /dev/null
+++ b/test/e2e/app-dir/actions-navigation/app/middleware-redirect/page.js
@@ -0,0 +1,3 @@
+export default function Page() {
+ return Hello World
+}
diff --git a/test/e2e/app-dir/actions-navigation/app/nested-folder/(foo)/product-category/[...slugs]/actions.js b/test/e2e/app-dir/actions-navigation/app/nested-folder/(foo)/product-category/[...slugs]/actions.js
new file mode 100644
index 0000000000000..b9761b427fe99
--- /dev/null
+++ b/test/e2e/app-dir/actions-navigation/app/nested-folder/(foo)/product-category/[...slugs]/actions.js
@@ -0,0 +1,5 @@
+'use server'
+
+export async function addToCart() {
+ console.log('addToCart')
+}
diff --git a/test/e2e/app-dir/actions-navigation/app/nested-folder/(foo)/product-category/[...slugs]/page.js b/test/e2e/app-dir/actions-navigation/app/nested-folder/(foo)/product-category/[...slugs]/page.js
new file mode 100644
index 0000000000000..3095ad98eff06
--- /dev/null
+++ b/test/e2e/app-dir/actions-navigation/app/nested-folder/(foo)/product-category/[...slugs]/page.js
@@ -0,0 +1,24 @@
+'use client'
+import { experimental_useFormStatus as useFormStatus } from 'react-dom'
+import { addToCart } from './actions'
+
+function SubmitButton() {
+ const { pending } = useFormStatus()
+
+ return (
+
+ )
+}
+
+export default async function Page() {
+ return (
+ <>
+ Add to cart
+
+ >
+ )
+}
diff --git a/test/e2e/app-dir/actions-navigation/app/nested-folder/[slug]/page.js b/test/e2e/app-dir/actions-navigation/app/nested-folder/[slug]/page.js
new file mode 100644
index 0000000000000..bd5a54fcab5c2
--- /dev/null
+++ b/test/e2e/app-dir/actions-navigation/app/nested-folder/[slug]/page.js
@@ -0,0 +1,5 @@
+import Link from 'next/link'
+
+export default async function Page() {
+ return Machines
+}
diff --git a/test/e2e/app-dir/actions-navigation/app/nested-folder/page.js b/test/e2e/app-dir/actions-navigation/app/nested-folder/page.js
new file mode 100644
index 0000000000000..abac7796778b9
--- /dev/null
+++ b/test/e2e/app-dir/actions-navigation/app/nested-folder/page.js
@@ -0,0 +1,7 @@
+import Link from 'next/link'
+
+export default function Page() {
+ return (
+ Go to ../action-after-redirect
+ )
+}
diff --git a/test/e2e/app-dir/actions-navigation/app/page.js b/test/e2e/app-dir/actions-navigation/app/page.js
new file mode 100644
index 0000000000000..5fef7be8a121f
--- /dev/null
+++ b/test/e2e/app-dir/actions-navigation/app/page.js
@@ -0,0 +1,9 @@
+import Link from 'next/link'
+
+export default function Page() {
+ return (
+
+ Go to /middleware-redirect
+
+ )
+}
diff --git a/test/e2e/app-dir/actions-navigation/index.test.ts b/test/e2e/app-dir/actions-navigation/index.test.ts
new file mode 100644
index 0000000000000..e6ab51a5b4c54
--- /dev/null
+++ b/test/e2e/app-dir/actions-navigation/index.test.ts
@@ -0,0 +1,49 @@
+import { createNextDescribe } from 'e2e-utils'
+import { check, waitFor } from 'next-test-utils'
+
+createNextDescribe(
+ 'app-dir action handling',
+ {
+ files: __dirname,
+ },
+ ({ next }) => {
+ it('should handle actions correctly after navigation / redirection events', async () => {
+ const browser = await next.browser('/')
+
+ await browser.elementByCss('#middleware-redirect').click()
+
+ expect(await browser.elementByCss('#form').text()).not.toContain(
+ 'Loading...'
+ )
+
+ await browser.elementByCss('#submit').click()
+
+ await check(() => {
+ return browser.elementByCss('#form').text()
+ }, /Loading.../)
+
+ // wait for 2 seconds, since the action takes a second to resolve
+ await waitFor(2000)
+
+ expect(await browser.elementByCss('#form').text()).not.toContain(
+ 'Loading...'
+ )
+
+ expect(await browser.elementByCss('#result').text()).toContain(
+ 'RESULT FROM SERVER ACTION'
+ )
+ })
+
+ it('should handle actions correctly after following a relative link', async () => {
+ const browser = await next.browser('/nested-folder/products')
+
+ await browser.elementByCss('a').click()
+
+ await browser.elementByCss('button').click()
+
+ await check(() => {
+ return (next.cliOutput.match(/addToCart/g) || []).length
+ }, 1)
+ })
+ }
+)
diff --git a/test/e2e/app-dir/actions-navigation/middleware.js b/test/e2e/app-dir/actions-navigation/middleware.js
new file mode 100644
index 0000000000000..45398601ca22f
--- /dev/null
+++ b/test/e2e/app-dir/actions-navigation/middleware.js
@@ -0,0 +1,9 @@
+import { NextResponse } from 'next/server'
+
+export function middleware(request) {
+ if (request.nextUrl.pathname.startsWith('/middleware-redirect')) {
+ return NextResponse.redirect(new URL('/action-after-redirect', request.url))
+ }
+}
+
+export const matcher = ['/middleware-redirect']
diff --git a/test/e2e/app-dir/actions-navigation/next.config.js b/test/e2e/app-dir/actions-navigation/next.config.js
new file mode 100644
index 0000000000000..6b81f43169761
--- /dev/null
+++ b/test/e2e/app-dir/actions-navigation/next.config.js
@@ -0,0 +1,5 @@
+module.exports = {
+ experimental: {
+ serverActions: true,
+ },
+}
diff --git a/test/e2e/app-dir/actions/app-action.test.ts b/test/e2e/app-dir/actions/app-action.test.ts
index e2a61af7f30cd..6a6865985e88f 100644
--- a/test/e2e/app-dir/actions/app-action.test.ts
+++ b/test/e2e/app-dir/actions/app-action.test.ts
@@ -1,6 +1,6 @@
/* eslint-disable jest/no-standalone-expect */
import { createNextDescribe } from 'e2e-utils'
-import { check } from 'next-test-utils'
+import { check, waitFor } from 'next-test-utils'
import { Request } from 'playwright-chromium'
import fs from 'fs-extra'
import { join } from 'path'
@@ -436,9 +436,7 @@ createNextDescribe(
it('should handle redirect to a relative URL in a single pass', async () => {
const browser = await next.browser('/client/edge')
- await new Promise((resolve) => {
- setTimeout(resolve, 3000)
- })
+ await waitFor(3000)
let requests = []
@@ -505,9 +503,7 @@ createNextDescribe(
it('should handle redirect to a relative URL in a single pass', async () => {
const browser = await next.browser('/client')
- await new Promise((resolve) => {
- setTimeout(resolve, 3000)
- })
+ await waitFor(3000)
let requests = []
diff --git a/test/e2e/app-dir/actions/app/test/page.js b/test/e2e/app-dir/actions/app/test/page.js
new file mode 100644
index 0000000000000..d91572fde352c
--- /dev/null
+++ b/test/e2e/app-dir/actions/app/test/page.js
@@ -0,0 +1,9 @@
+import Link from 'next/link'
+
+export default function Page() {
+ return (
+
+ Go to /middleware-redirect
+
+ )
+}