diff --git a/.changeset/rare-dodos-push.md b/.changeset/rare-dodos-push.md new file mode 100644 index 00000000000..f8154384efa --- /dev/null +++ b/.changeset/rare-dodos-push.md @@ -0,0 +1,5 @@ +--- +"@remix-run/server-runtime": patch +--- + +Don't log thrown response stubs via `handleError` in Single Fetch diff --git a/integration/single-fetch-test.ts b/integration/single-fetch-test.ts index 7b1abd76f95..ad0a3cf4fac 100644 --- a/integration/single-fetch-test.ts +++ b/integration/single-fetch-test.ts @@ -1667,6 +1667,81 @@ test.describe("single-fetch", () => { ); }); + test("does not log thrown redirect response stubs via handleError", async () => { + let fixture = await createFixture({ + config: { + future: { + unstable_singleFetch: true, + }, + }, + files: { + ...files, + "app/routes/redirect.tsx": js` + export function action({ response }) { + response.status = 301; + response.headers.set("Location", "/data"); + throw response; + } + export function loader({ response }) { + response.status = 301; + response.headers.set("Location", "/data"); + throw response; + } + export default function Component() { + return

Should not see me

; + } + `, + }, + }); + + let errorLogs = []; + console.error = (e) => errorLogs.push(e); + await fixture.requestDocument("/redirect"); + await fixture.requestSingleFetchData("/redirect.data"); + await fixture.requestSingleFetchData("/redirect.data", { + method: "post", + body: null, + }); + expect(errorLogs.length).toBe(0); + }); + + test("does not log thrown non-redirect response stubs via handleError", async () => { + let fixture = await createFixture({ + config: { + future: { + unstable_singleFetch: true, + }, + }, + files: { + ...files, + "app/routes/redirect.tsx": js` + export function action({ response }) { + response.status = 400; + throw response; + } + export function loader({ response }) { + response.status = 400; + throw response; + } + export default function Component() { + return

Should not see me

; + } + `, + }, + }); + + let errorLogs = []; + console.error = (e) => errorLogs.push(e); + await fixture.requestDocument("/redirect"); + expect(errorLogs.length).toBe(1); // ErrorBoundary render logs this + await fixture.requestSingleFetchData("/redirect.data"); + await fixture.requestSingleFetchData("/redirect.data", { + method: "post", + body: null, + }); + expect(errorLogs.length).toBe(1); + }); + test.describe("client loaders", () => { test("when no routes have client loaders", async ({ page }) => { let fixture = await createFixture( diff --git a/packages/remix-server-runtime/server.ts b/packages/remix-server-runtime/server.ts index 11305551c9c..e73f7308fb6 100644 --- a/packages/remix-server-runtime/server.ts +++ b/packages/remix-server-runtime/server.ts @@ -40,6 +40,7 @@ import { getSingleFetchDataStrategy, getSingleFetchRedirect, getSingleFetchResourceRouteDataStrategy, + isResponseStub, mergeResponseStubs, singleFetchAction, singleFetchLoaders, @@ -408,17 +409,6 @@ async function handleDocumentRequest( return context; } - // Sanitize errors outside of development environments - if (context.errors) { - Object.values(context.errors).forEach((err) => { - // @ts-expect-error This is "private" from users but intended for internal use - if (!isRouteErrorResponse(err) || err.error) { - handleError(err); - } - }); - context.errors = sanitizeErrors(context.errors, serverMode); - } - let statusCode: number; let headers: Headers; if (build.future.unstable_singleFetch) { @@ -437,6 +427,17 @@ async function handleDocumentRequest( headers = getDocumentHeaders(build, context); } + // Sanitize errors outside of development environments + if (context.errors) { + Object.values(context.errors).forEach((err) => { + // @ts-expect-error `err.error` is "private" from users but intended for internal use + if ((!isRouteErrorResponse(err) || err.error) && !isResponseStub(err)) { + handleError(err); + } + }); + context.errors = sanitizeErrors(context.errors, serverMode); + } + // Server UI state to send to the client. // - When single fetch is enabled, this is streamed down via `serverHandoffStream` // - Otherwise it's stringified into `serverHandoffString` diff --git a/packages/remix-server-runtime/single-fetch.ts b/packages/remix-server-runtime/single-fetch.ts index bdfe745f7fc..c9303e79be0 100644 --- a/packages/remix-server-runtime/single-fetch.ts +++ b/packages/remix-server-runtime/single-fetch.ts @@ -184,7 +184,7 @@ export async function singleFetchAction( if (context.errors) { Object.values(context.errors).forEach((err) => { // @ts-expect-error This is "private" from users but intended for internal use - if (!isRouteErrorResponse(err) || err.error) { + if ((!isRouteErrorResponse(err) || err.error) && !isResponseStub(err)) { handleError(err); } }); @@ -273,7 +273,7 @@ export async function singleFetchLoaders( if (context.errors) { Object.values(context.errors).forEach((err) => { // @ts-expect-error This is "private" from users but intended for internal use - if (!isRouteErrorResponse(err) || err.error) { + if ((!isRouteErrorResponse(err) || err.error) && !isResponseStub(err)) { handleError(err); } });