From 55746d07cfb8b575d637991eb7c8821c3d05bc9b Mon Sep 17 00:00:00 2001 From: Alexander Savelyev <91429106+vordgi@users.noreply.github.com> Date: Wed, 30 Oct 2024 00:12:27 +0400 Subject: [PATCH] Fix relative redirect (#71932) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Fixing a bug ## What? Changed the formation of the redirect url to the correct way. _It’s interesting that you had tests for this, but since only the main page was tested, it wasn’t caught._ Expanded the tests, made a separate method to assign location, and described it. ```js new URL('./relative', 'https://example.com/subdir').href // 'https://example.com/relative' new URL('./relative', 'https://example.com/subdir/').href // 'https://example.com/subdir/relative' ``` Fixes #71906 [#65893, #67966] --------- Co-authored-by: Zack Tanner <1939140+ztanner@users.noreply.github.com> --- packages/next/src/client/assign-location.ts | 22 +++++++++++++ .../reducers/server-action-reducer.ts | 7 ++-- .../app/subdir/page.tsx | 32 +++++++++++++++++++ .../app/subdir/subpage/page.tsx | 3 ++ .../server-actions-relative-redirect.test.ts | 26 +++++++++++++++ 5 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 packages/next/src/client/assign-location.ts create mode 100644 test/e2e/app-dir/server-actions-relative-redirect/app/subdir/page.tsx create mode 100644 test/e2e/app-dir/server-actions-relative-redirect/app/subdir/subpage/page.tsx diff --git a/packages/next/src/client/assign-location.ts b/packages/next/src/client/assign-location.ts new file mode 100644 index 00000000000000..0846b5c0e1735a --- /dev/null +++ b/packages/next/src/client/assign-location.ts @@ -0,0 +1,22 @@ +import { addBasePath } from './add-base-path' + +/** + * Function to correctly assign location to URL + * + * The method will add basePath, and will also correctly add location (including if it is a relative path) + * @param location Location that should be added to the url + * @param url Base URL to which the location should be assigned + */ +export function assignLocation(location: string, url: URL): URL { + if (location.startsWith('.')) { + const urlBase = url.origin + url.pathname + return new URL( + // In order for a relative path to be added to the current url correctly, the current url must end with a slash + // new URL('./relative', 'https://example.com/subdir').href -> 'https://example.com/relative' + // new URL('./relative', 'https://example.com/subdir/').href -> 'https://example.com/subdir/relative' + (urlBase.endsWith('/') ? urlBase : urlBase + '/') + location + ) + } + + return new URL(addBasePath(location), url.href) +} 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 2f9e227a66245a..b99614d3e427c9 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 @@ -31,7 +31,7 @@ import { type ServerActionAction, type ServerActionMutable, } from '../router-reducer-types' -import { addBasePath } from '../../../add-base-path' +import { assignLocation } from '../../../assign-location' import { createHrefFromUrl } from '../create-href-from-url' import { handleExternalUrl } from './navigate-reducer' import { applyRouterStatePatchToTree } from '../apply-router-state-patch-to-tree' @@ -129,9 +129,8 @@ async function fetchServerAction( } const redirectLocation = location - ? new URL( - addBasePath(location), - // Ensure relative redirects in Server Actions work, e.g. redirect('./somewhere-else') + ? assignLocation( + location, new URL(state.canonicalUrl, window.location.href) ) : undefined diff --git a/test/e2e/app-dir/server-actions-relative-redirect/app/subdir/page.tsx b/test/e2e/app-dir/server-actions-relative-redirect/app/subdir/page.tsx new file mode 100644 index 00000000000000..d46df8f35158f4 --- /dev/null +++ b/test/e2e/app-dir/server-actions-relative-redirect/app/subdir/page.tsx @@ -0,0 +1,32 @@ +'use client' + +import { startTransition } from 'react' +import { absoluteRedirect, relativeRedirect } from '../actions' + +export default function Page() { + return ( + <> +

hello subdir page

+ + + + ) +} diff --git a/test/e2e/app-dir/server-actions-relative-redirect/app/subdir/subpage/page.tsx b/test/e2e/app-dir/server-actions-relative-redirect/app/subdir/subpage/page.tsx new file mode 100644 index 00000000000000..b485d8d3257253 --- /dev/null +++ b/test/e2e/app-dir/server-actions-relative-redirect/app/subdir/subpage/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return

hello subdir nested page

+} diff --git a/test/e2e/app-dir/server-actions-relative-redirect/server-actions-relative-redirect.test.ts b/test/e2e/app-dir/server-actions-relative-redirect/server-actions-relative-redirect.test.ts index 671e8397b214ea..081efc9ade3717 100644 --- a/test/e2e/app-dir/server-actions-relative-redirect/server-actions-relative-redirect.test.ts +++ b/test/e2e/app-dir/server-actions-relative-redirect/server-actions-relative-redirect.test.ts @@ -32,4 +32,30 @@ describe('server-actions-relative-redirect', () => { return 'success' }, 'success') }) + + it('should work with relative redirect from subdir', async () => { + const browser = await next.browser('/subdir') + await browser.waitForElementByCss('#relative-subdir-redirect').click() + + await check(async () => { + expect(await browser.waitForElementByCss('#page-loaded').text()).toBe( + 'hello subdir nested page' + ) + + return 'success' + }, 'success') + }) + + it('should work with absolute redirect from subdir', async () => { + const browser = await next.browser('/subdir') + await browser.waitForElementByCss('#absolute-subdir-redirect').click() + + await check(async () => { + expect(await browser.waitForElementByCss('#page-loaded').text()).toBe( + 'hello nested page' + ) + + return 'success' + }, 'success') + }) })