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 ( - + <> + + + + ) }