Skip to content

Commit 671ffcf

Browse files
committed
feat: support handleError export from entry.server
1 parent fc512e5 commit 671ffcf

File tree

6 files changed

+294
-71
lines changed

6 files changed

+294
-71
lines changed

.changeset/handle-error.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@remix-run/server-runtime": minor
3+
---
4+
5+
Add optional `handleError` export to entry.server for custom server-side error processing

integration/error-sanitization-test.ts

+190
Original file line numberDiff line numberDiff line change
@@ -417,4 +417,194 @@ test.describe("Error Sanitization", () => {
417417
expect(errorLogs[0][0].stack).toMatch(" at ");
418418
});
419419
});
420+
421+
test.describe("serverMode=production (user-provided handleError)", () => {
422+
test.beforeAll(async () => {
423+
fixture = await createFixture(
424+
{
425+
files: {
426+
"app/entry.server.tsx": js`
427+
import type { EntryContext } from "@remix-run/node";
428+
import { RemixServer, isRouteErrorResponse } from "@remix-run/react";
429+
import { renderToString } from "react-dom/server";
430+
431+
export default function handleRequest(
432+
request: Request,
433+
responseStatusCode: number,
434+
responseHeaders: Headers,
435+
remixContext: EntryContext
436+
) {
437+
let markup = renderToString(
438+
<RemixServer context={remixContext} url={request.url} />
439+
);
440+
441+
responseHeaders.set("Content-Type", "text/html");
442+
443+
return new Response("<!DOCTYPE html>" + markup, {
444+
status: responseStatusCode,
445+
headers: responseHeaders,
446+
});
447+
}
448+
449+
export function handleError(error: unknown, { request }: { request: Request }) {
450+
console.error("App Specific Error Logging:");
451+
console.error(" Request: " + request.method + " " + request.url);
452+
debugger;
453+
let msg;
454+
if (isRouteErrorResponse(error)) {
455+
console.error(" Error: " + error.status + " " + error.statusText);
456+
} else if (error instanceof Error) {
457+
console.error(" Error: " + error.message);
458+
console.error(" Stack: " + error.stack);
459+
} else {
460+
console.error("Dunno what this is");
461+
}
462+
}
463+
`,
464+
...routeFiles,
465+
},
466+
},
467+
ServerMode.Production
468+
);
469+
});
470+
471+
test("renders document without errors", async () => {
472+
let response = await fixture.requestDocument("/");
473+
let html = await response.text();
474+
expect(html).toMatch("Index Route");
475+
expect(html).toMatch("LOADER");
476+
expect(html).not.toMatch("MESSAGE:");
477+
expect(html).not.toMatch(/stack/i);
478+
});
479+
480+
test("sanitizes loader errors in document requests", async () => {
481+
let response = await fixture.requestDocument("/?loader");
482+
let html = await response.text();
483+
expect(html).toMatch("Index Error");
484+
expect(html).not.toMatch("LOADER");
485+
expect(html).toMatch("MESSAGE:Unexpected Server Error");
486+
expect(html).toMatch(
487+
'{"routes/index":{"message":"Unexpected Server Error","__type":"Error"}}'
488+
);
489+
expect(html).not.toMatch(/stack/i);
490+
expect(errorLogs[0][0]).toEqual("App Specific Error Logging:");
491+
expect(errorLogs[1][0]).toEqual(" Request: GET test://test/?loader");
492+
expect(errorLogs[2][0]).toEqual(" Error: Loader Error");
493+
expect(errorLogs[3][0]).toMatch(" at ");
494+
expect(errorLogs.length).toBe(4);
495+
});
496+
497+
test("sanitizes render errors in document requests", async () => {
498+
let response = await fixture.requestDocument("/?render");
499+
let html = await response.text();
500+
expect(html).toMatch("Index Error");
501+
expect(html).toMatch("MESSAGE:Unexpected Server Error");
502+
expect(html).toMatch(
503+
'{"routes/index":{"message":"Unexpected Server Error","__type":"Error"}}'
504+
);
505+
expect(html).not.toMatch(/stack/i);
506+
expect(errorLogs[0][0]).toEqual("App Specific Error Logging:");
507+
expect(errorLogs[1][0]).toEqual(" Request: GET test://test/?render");
508+
expect(errorLogs[2][0]).toEqual(" Error: Render Error");
509+
expect(errorLogs[3][0]).toMatch(" at ");
510+
expect(errorLogs.length).toBe(4);
511+
});
512+
513+
test("renders deferred document without errors", async () => {
514+
let response = await fixture.requestDocument("/defer");
515+
let html = await response.text();
516+
expect(html).toMatch("Defer Route");
517+
expect(html).toMatch("RESOLVED");
518+
expect(html).not.toMatch("MESSAGE:");
519+
// Defer errors are not not part of the JSON blob but rather rejected
520+
// against a pending promise and therefore are inlined JS.
521+
expect(html).not.toMatch("x.stack=e.stack;");
522+
});
523+
524+
test("sanitizes defer errors in document requests", async () => {
525+
let response = await fixture.requestDocument("/defer?loader");
526+
let html = await response.text();
527+
expect(html).toMatch("Defer Error");
528+
expect(html).not.toMatch("RESOLVED");
529+
expect(html).toMatch('{"message":"Unexpected Server Error"}');
530+
// Defer errors are not not part of the JSON blob but rather rejected
531+
// against a pending promise and therefore are inlined JS.
532+
expect(html).toMatch("x.stack=undefined;");
533+
// defer errors are not logged to the server console since the request
534+
// has "succeeded"
535+
expect(errorLogs.length).toBe(0);
536+
});
537+
538+
test("returns data without errors", async () => {
539+
let response = await fixture.requestData("/", "routes/index");
540+
let text = await response.text();
541+
expect(text).toMatch("LOADER");
542+
expect(text).not.toMatch("MESSAGE:");
543+
expect(text).not.toMatch(/stack/i);
544+
});
545+
546+
test("sanitizes loader errors in data requests", async () => {
547+
let response = await fixture.requestData("/?loader", "routes/index");
548+
let text = await response.text();
549+
expect(text).toBe('{"message":"Unexpected Server Error"}');
550+
expect(errorLogs[0][0]).toEqual("App Specific Error Logging:");
551+
expect(errorLogs[1][0]).toEqual(
552+
" Request: GET test://test/?loader=&_data=routes%2Findex"
553+
);
554+
expect(errorLogs[2][0]).toEqual(" Error: Loader Error");
555+
expect(errorLogs[3][0]).toMatch(" at ");
556+
expect(errorLogs.length).toBe(4);
557+
});
558+
559+
test("returns deferred data without errors", async () => {
560+
let response = await fixture.requestData("/defer", "routes/defer");
561+
let text = await response.text();
562+
expect(text).toMatch("RESOLVED");
563+
expect(text).not.toMatch("REJECTED");
564+
expect(text).not.toMatch(/stack/i);
565+
});
566+
567+
test("sanitizes loader errors in deferred data requests", async () => {
568+
let response = await fixture.requestData("/defer?loader", "routes/defer");
569+
let text = await response.text();
570+
expect(text).toBe(
571+
'{"lazy":"__deferred_promise:lazy"}\n\n' +
572+
'error:{"lazy":{"message":"Unexpected Server Error"}}\n\n'
573+
);
574+
// defer errors are not logged to the server console since the request
575+
// has "succeeded"
576+
expect(errorLogs.length).toBe(0);
577+
});
578+
579+
test("sanitizes loader errors in resource requests", async () => {
580+
let response = await fixture.requestData(
581+
"/resource?loader",
582+
"routes/resource"
583+
);
584+
let text = await response.text();
585+
expect(text).toBe('{"message":"Unexpected Server Error"}');
586+
expect(errorLogs[0][0]).toEqual("App Specific Error Logging:");
587+
expect(errorLogs[1][0]).toEqual(
588+
" Request: GET test://test/resource?loader=&_data=routes%2Fresource"
589+
);
590+
expect(errorLogs[2][0]).toEqual(" Error: Loader Error");
591+
expect(errorLogs[3][0]).toMatch(" at ");
592+
expect(errorLogs.length).toBe(4);
593+
});
594+
595+
test("sanitizes mismatched route errors in data requests", async () => {
596+
let response = await fixture.requestData("/", "not-a-route");
597+
let text = await response.text();
598+
expect(text).toBe('{"message":"Unexpected Server Error"}');
599+
expect(errorLogs[0][0]).toEqual("App Specific Error Logging:");
600+
expect(errorLogs[1][0]).toEqual(
601+
" Request: GET test://test/?_data=not-a-route"
602+
);
603+
expect(errorLogs[2][0]).toEqual(
604+
' Error: Route "not-a-route" does not match URL "/"'
605+
);
606+
expect(errorLogs[3][0]).toMatch(" at ");
607+
expect(errorLogs.length).toBe(4);
608+
});
609+
});
420610
});

0 commit comments

Comments
 (0)