diff --git a/.changeset/odd-frogs-attend.md b/.changeset/odd-frogs-attend.md new file mode 100644 index 00000000000..18907021cc2 --- /dev/null +++ b/.changeset/odd-frogs-attend.md @@ -0,0 +1,5 @@ +--- +"@remix-run/server-runtime": patch +--- + +handle net new redirects created by handleDataRequest diff --git a/packages/remix-server-runtime/__tests__/server-test.ts b/packages/remix-server-runtime/__tests__/server-test.ts index a31e23e13d8..1195e4fdaf5 100644 --- a/packages/remix-server-runtime/__tests__/server-test.ts +++ b/packages/remix-server-runtime/__tests__/server-test.ts @@ -903,6 +903,46 @@ describe("shared server runtime", () => { expect(indexAction.mock.calls.length).toBe(1); }); + test("data request handleDataRequest redirects are handled", async () => { + let rootLoader = jest.fn(() => { + return "root"; + }); + let indexLoader = jest.fn(() => { + return "index"; + }); + let build = mockServerBuild({ + root: { + default: {}, + loader: rootLoader, + }, + "routes/_index": { + parentId: "root", + loader: indexLoader, + index: true, + }, + }); + build.entry.module.handleDataRequest.mockImplementation(async () => { + return new Response(null, { + status: 302, + headers: { + Location: "/redirect", + }, + }); + }); + let handler = createRequestHandler(build, ServerMode.Test); + + let request = new Request(`${baseUrl}/?_data=routes/_index`, { + method: "get", + }); + + let result = await handler(request); + expect(result.status).toBe(204); + expect(result.headers.get("X-Remix-Redirect")).toBe("/redirect"); + expect(result.headers.get("X-Remix-Status")).toBe("302"); + expect(rootLoader.mock.calls.length).toBe(0); + expect(indexLoader.mock.calls.length).toBe(1); + }); + test("aborts request", async () => { let rootLoader = jest.fn(() => { return "root"; diff --git a/packages/remix-server-runtime/server.ts b/packages/remix-server-runtime/server.ts index b4f03e55640..ccf4c3e6ce4 100644 --- a/packages/remix-server-runtime/server.ts +++ b/packages/remix-server-runtime/server.ts @@ -151,6 +151,10 @@ export const createRequestHandler: CreateRequestHandlerFunction = ( params, request, }); + + if (isRedirectResponse(response)) { + response = createRemixRedirectResponse(response, _build.basename); + } } } else if ( _build.future.unstable_singleFetch && @@ -184,6 +188,10 @@ export const createRequestHandler: CreateRequestHandlerFunction = ( params, request, }); + + if (isRedirectResponse(response)) { + response = createRemixRedirectResponse(response, _build.basename); + } } } else if ( matches && @@ -243,27 +251,7 @@ async function handleDataRequest( }); if (isRedirectResponse(response)) { - // We don't have any way to prevent a fetch request from following - // redirects. So we use the `X-Remix-Redirect` header to indicate the - // next URL, and then "follow" the redirect manually on the client. - let headers = new Headers(response.headers); - let redirectUrl = headers.get("Location")!; - headers.set( - "X-Remix-Redirect", - build.basename - ? stripBasename(redirectUrl, build.basename) || redirectUrl - : redirectUrl - ); - headers.set("X-Remix-Status", response.status); - headers.delete("Location"); - if (response.headers.get("Set-Cookie") !== null) { - headers.set("X-Remix-Revalidate", "yes"); - } - - return new Response(null, { - status: 204, - headers, - }); + return createRemixRedirectResponse(response, build.basename); } if (DEFERRED_SYMBOL in response) { @@ -848,3 +836,28 @@ function encodeViaTurboStream( ], }); } + +function createRemixRedirectResponse( + response: Response, + basename: string | undefined +) { + // We don't have any way to prevent a fetch request from following + // redirects. So we use the `X-Remix-Redirect` header to indicate the + // next URL, and then "follow" the redirect manually on the client. + let headers = new Headers(response.headers); + let redirectUrl = headers.get("Location")!; + headers.set( + "X-Remix-Redirect", + basename ? stripBasename(redirectUrl, basename) || redirectUrl : redirectUrl + ); + headers.set("X-Remix-Status", String(response.status)); + headers.delete("Location"); + if (response.headers.get("Set-Cookie") !== null) { + headers.set("X-Remix-Revalidate", "yes"); + } + + return new Response(null, { + status: 204, + headers, + }); +}