From aec262c2560bcdf2b0ae8bba202b04f24e1858ce Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Fri, 9 Aug 2024 14:42:42 -0400 Subject: [PATCH] Fix single fetch redirects when a basename is present --- .changeset/rich-teachers-warn.md | 5 ++ integration/single-fetch-test.ts | 65 +++++++++++++++++++ packages/remix-server-runtime/single-fetch.ts | 30 +++++++-- 3 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 .changeset/rich-teachers-warn.md diff --git a/.changeset/rich-teachers-warn.md b/.changeset/rich-teachers-warn.md new file mode 100644 index 00000000000..c07df3aa7c8 --- /dev/null +++ b/.changeset/rich-teachers-warn.md @@ -0,0 +1,5 @@ +--- +"@remix-run/server-runtime": patch +--- + +Single Fetch: Fix redirects when a `basename` is presernt diff --git a/integration/single-fetch-test.ts b/integration/single-fetch-test.ts index e383ff812ab..aad153c0d41 100644 --- a/integration/single-fetch-test.ts +++ b/integration/single-fetch-test.ts @@ -1463,6 +1463,71 @@ test.describe("single-fetch", () => { expect(await app.getHtml("#target")).toContain("Target"); }); + test("processes redirects when a basename is present", async ({ page }) => { + let fixture = await createFixture({ + compiler: "vite", + files: { + ...files, + "vite.config.ts": js` + import { defineConfig } from "vite"; + import { vitePlugin as remix } from "@remix-run/dev"; + export default defineConfig({ + plugins: [ + remix({ + basename: '/base', + future: { + unstable_singleFetch: true, + } + }), + ], + }); + `, + "app/routes/data.tsx": js` + import { redirect } from '@remix-run/node'; + export function loader() { + throw redirect('/target'); + } + export default function Component() { + return null + } + `, + "app/routes/target.tsx": js` + export default function Component() { + return

Target

+ } + `, + }, + }); + + console.error = () => {}; + + let res = await fixture.requestDocument("/base/data"); + expect(res.status).toBe(302); + expect(res.headers.get("Location")).toBe("/base/target"); + expect(await res.text()).toBe(""); + + let { status, data } = await fixture.requestSingleFetchData( + "/base/data.data" + ); + expect(data).toEqual({ + [SingleFetchRedirectSymbol]: { + status: 302, + redirect: "/target", + reload: false, + replace: false, + revalidate: false, + }, + }); + expect(status).toBe(202); + + let appFixture = await createAppFixture(fixture); + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/base/"); + await app.clickLink("/base/data"); + await page.waitForSelector("#target"); + expect(await app.getHtml("#target")).toContain("Target"); + }); + test("processes thrown loader errors", async ({ page }) => { let fixture = await createFixture({ config: { diff --git a/packages/remix-server-runtime/single-fetch.ts b/packages/remix-server-runtime/single-fetch.ts index e9298122a7b..b1c888f033a 100644 --- a/packages/remix-server-runtime/single-fetch.ts +++ b/packages/remix-server-runtime/single-fetch.ts @@ -8,6 +8,7 @@ import { isRouteErrorResponse, unstable_data as routerData, UNSAFE_ErrorResponseImpl as ErrorResponseImpl, + stripBasename, } from "@remix-run/router"; import { encode } from "turbo-stream"; @@ -111,7 +112,11 @@ export async function singleFetchAction( // let non-Response return values through if (isResponse(result)) { return { - result: getSingleFetchRedirect(result.status, result.headers), + result: getSingleFetchRedirect( + result.status, + result.headers, + build.basename + ), headers: result.headers, status: SINGLE_FETCH_REDIRECT_STATUS, }; @@ -122,7 +127,11 @@ export async function singleFetchAction( if (isRedirectStatusCode(context.statusCode) && headers.has("Location")) { return { - result: getSingleFetchRedirect(context.statusCode, headers), + result: getSingleFetchRedirect( + context.statusCode, + headers, + build.basename + ), headers, status: SINGLE_FETCH_REDIRECT_STATUS, }; @@ -192,7 +201,8 @@ export async function singleFetchLoaders( result: { [SingleFetchRedirectSymbol]: getSingleFetchRedirect( result.status, - result.headers + result.headers, + build.basename ), }, headers: result.headers, @@ -208,7 +218,8 @@ export async function singleFetchLoaders( result: { [SingleFetchRedirectSymbol]: getSingleFetchRedirect( context.statusCode, - headers + headers, + build.basename ), }, headers, @@ -264,10 +275,17 @@ export async function singleFetchLoaders( export function getSingleFetchRedirect( status: number, - headers: Headers + headers: Headers, + basename: string | undefined ): SingleFetchRedirectResult { + let redirect = headers.get("Location")!; + + if (basename) { + redirect = stripBasename(redirect, basename) || redirect; + } + return { - redirect: headers.get("Location")!, + redirect, status, revalidate: // Technically X-Remix-Revalidate isn't needed here - that was an implementation