From 2c80a0f53590173ffd52fce30734399d75fcde3b Mon Sep 17 00:00:00 2001
From: Zack Tanner <1939140+ztanner@users.noreply.github.com>
Date: Fri, 26 Apr 2024 12:55:57 -0700
Subject: [PATCH] update redirect handling on forwarded action requests
(#65097)
If a forwarded server action redirects, don't attempt to follow the
redirect as it can lead to errors. For example, if the forwarded request
followed a redirect to the same URL and had to forward the request
again, it'd hit a loop. The redirect will be handled by the
`X-Action-Redirect` which will be set when the forwarded action hits the
`createRedirectRenderResult` case.
Closes NEXT-3243
---
.../src/server/app-render/action-handler.ts | 5 ++-
test/e2e/app-dir/actions/app-action.test.ts | 34 +++++++++++++++++++
.../actions/app/delayed-action/actions.ts | 7 ++++
.../actions/app/delayed-action/button.tsx | 23 ++++++++++---
4 files changed, 64 insertions(+), 5 deletions(-)
diff --git a/packages/next/src/server/app-render/action-handler.ts b/packages/next/src/server/app-render/action-handler.ts
index 80f373f2777e7..5cf3c9e2c6fc9 100644
--- a/packages/next/src/server/app-render/action-handler.ts
+++ b/packages/next/src/server/app-render/action-handler.ts
@@ -206,6 +206,7 @@ async function createForwardedActionResponse(
body,
duplex: 'half',
headers: forwardedHeaders,
+ redirect: 'manual',
next: {
// @ts-ignore
internal: 1,
@@ -226,9 +227,11 @@ async function createForwardedActionResponse(
response.body?.cancel()
}
} catch (err) {
- // we couldn't stream the forwarded response, so we'll just do a normal redirect
+ // we couldn't stream the forwarded response, so we'll just return an empty response
console.error(`failed to forward action response`, err)
}
+
+ return RenderResult.fromStatic('{}')
}
async function createRedirectRenderResult(
diff --git a/test/e2e/app-dir/actions/app-action.test.ts b/test/e2e/app-dir/actions/app-action.test.ts
index 043e9b87fd9e0..b1f964f4c0640 100644
--- a/test/e2e/app-dir/actions/app-action.test.ts
+++ b/test/e2e/app-dir/actions/app-action.test.ts
@@ -598,6 +598,40 @@ describe('app-dir action handling', () => {
}
)
+ it.each(['node', 'edge'])(
+ 'should not error when a forwarded action triggers a redirect (%s)',
+ async (runtime) => {
+ let redirectResponseCode
+ const browser = await next.browser(`/delayed-action/${runtime}`, {
+ beforePageLoad(page) {
+ page.on('response', async (res: Response) => {
+ const headers = await res.allHeaders()
+ if (headers['x-action-redirect']) {
+ redirectResponseCode = res.status()
+ }
+ })
+ },
+ })
+
+ // Trigger the delayed action. This will sleep for a few seconds before dispatching the server action handler
+ await browser.elementById('run-action-redirect').click()
+
+ // navigate away from the page
+ await browser
+ .elementByCss(`[href='/delayed-action/${runtime}/other']`)
+ .click()
+ .waitForElementByCss('#other-page')
+
+ // confirm a successful response code on the redirected action
+ await retry(async () => {
+ expect(redirectResponseCode).toBe(200)
+ })
+
+ // confirm that the redirect was handled
+ await browser.waitForElementByCss('#run-action-redirect')
+ }
+ )
+
if (isNextStart) {
it('should not expose action content in sourcemaps', async () => {
const sourcemap = (
diff --git a/test/e2e/app-dir/actions/app/delayed-action/actions.ts b/test/e2e/app-dir/actions/app/delayed-action/actions.ts
index c4493473cae2a..94b37998dbee4 100644
--- a/test/e2e/app-dir/actions/app/delayed-action/actions.ts
+++ b/test/e2e/app-dir/actions/app/delayed-action/actions.ts
@@ -1,8 +1,15 @@
'use server'
import { revalidatePath } from 'next/cache'
+import { redirect } from 'next/navigation'
export const action = async () => {
console.log('revalidating')
revalidatePath('/delayed-action', 'page')
return Math.random()
}
+
+export const redirectAction = async () => {
+ // sleep for 500ms
+ await new Promise((res) => setTimeout(res, 500))
+ redirect('/delayed-action/node')
+}
diff --git a/test/e2e/app-dir/actions/app/delayed-action/button.tsx b/test/e2e/app-dir/actions/app/delayed-action/button.tsx
index 30012948856da..b38b15d406814 100644
--- a/test/e2e/app-dir/actions/app/delayed-action/button.tsx
+++ b/test/e2e/app-dir/actions/app/delayed-action/button.tsx
@@ -1,7 +1,7 @@
'use client'
import { useContext } from 'react'
-import { action } from './actions'
+import { action, redirectAction } from './actions'
import { DataContext } from './context'
export function Button() {
@@ -13,9 +13,24 @@ export function Button() {
setData(result)
}
+
+ const handleRedirect = async () => {
+ await new Promise((res) => setTimeout(res, 1000))
+
+ const result = await redirectAction()
+
+ setData(result)
+ }
+
return (
-
+ <>
+
+
+
+ >
)
}