Skip to content

Commit b5dc636

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

File tree

6 files changed

+544
-322
lines changed

6 files changed

+544
-322
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

+189
Original file line numberDiff line numberDiff line change
@@ -417,4 +417,193 @@ 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+
let msg;
453+
if (isRouteErrorResponse(error)) {
454+
console.error(" Error: " + error.status + " " + error.statusText);
455+
} else if (error instanceof Error) {
456+
console.error(" Error: " + error.message);
457+
console.error(" Stack: " + error.stack);
458+
} else {
459+
console.error("Dunno what this is");
460+
}
461+
}
462+
`,
463+
...routeFiles,
464+
},
465+
},
466+
ServerMode.Production
467+
);
468+
});
469+
470+
test("renders document without errors", async () => {
471+
let response = await fixture.requestDocument("/");
472+
let html = await response.text();
473+
expect(html).toMatch("Index Route");
474+
expect(html).toMatch("LOADER");
475+
expect(html).not.toMatch("MESSAGE:");
476+
expect(html).not.toMatch(/stack/i);
477+
});
478+
479+
test("sanitizes loader errors in document requests", async () => {
480+
let response = await fixture.requestDocument("/?loader");
481+
let html = await response.text();
482+
expect(html).toMatch("Index Error");
483+
expect(html).not.toMatch("LOADER");
484+
expect(html).toMatch("MESSAGE:Unexpected Server Error");
485+
expect(html).toMatch(
486+
'{"routes/index":{"message":"Unexpected Server Error","__type":"Error"}}'
487+
);
488+
expect(html).not.toMatch(/stack/i);
489+
expect(errorLogs[0][0]).toEqual("App Specific Error Logging:");
490+
expect(errorLogs[1][0]).toEqual(" Request: GET test://test/?loader");
491+
expect(errorLogs[2][0]).toEqual(" Error: Loader Error");
492+
expect(errorLogs[3][0]).toMatch(" at ");
493+
expect(errorLogs.length).toBe(4);
494+
});
495+
496+
test("sanitizes render errors in document requests", async () => {
497+
let response = await fixture.requestDocument("/?render");
498+
let html = await response.text();
499+
expect(html).toMatch("Index Error");
500+
expect(html).toMatch("MESSAGE:Unexpected Server Error");
501+
expect(html).toMatch(
502+
'{"routes/index":{"message":"Unexpected Server Error","__type":"Error"}}'
503+
);
504+
expect(html).not.toMatch(/stack/i);
505+
expect(errorLogs[0][0]).toEqual("App Specific Error Logging:");
506+
expect(errorLogs[1][0]).toEqual(" Request: GET test://test/?render");
507+
expect(errorLogs[2][0]).toEqual(" Error: Render Error");
508+
expect(errorLogs[3][0]).toMatch(" at ");
509+
expect(errorLogs.length).toBe(4);
510+
});
511+
512+
test("renders deferred document without errors", async () => {
513+
let response = await fixture.requestDocument("/defer");
514+
let html = await response.text();
515+
expect(html).toMatch("Defer Route");
516+
expect(html).toMatch("RESOLVED");
517+
expect(html).not.toMatch("MESSAGE:");
518+
// Defer errors are not not part of the JSON blob but rather rejected
519+
// against a pending promise and therefore are inlined JS.
520+
expect(html).not.toMatch("x.stack=e.stack;");
521+
});
522+
523+
test("sanitizes defer errors in document requests", async () => {
524+
let response = await fixture.requestDocument("/defer?loader");
525+
let html = await response.text();
526+
expect(html).toMatch("Defer Error");
527+
expect(html).not.toMatch("RESOLVED");
528+
expect(html).toMatch('{"message":"Unexpected Server Error"}');
529+
// Defer errors are not not part of the JSON blob but rather rejected
530+
// against a pending promise and therefore are inlined JS.
531+
expect(html).toMatch("x.stack=undefined;");
532+
// defer errors are not logged to the server console since the request
533+
// has "succeeded"
534+
expect(errorLogs.length).toBe(0);
535+
});
536+
537+
test("returns data without errors", async () => {
538+
let response = await fixture.requestData("/", "routes/index");
539+
let text = await response.text();
540+
expect(text).toMatch("LOADER");
541+
expect(text).not.toMatch("MESSAGE:");
542+
expect(text).not.toMatch(/stack/i);
543+
});
544+
545+
test("sanitizes loader errors in data requests", async () => {
546+
let response = await fixture.requestData("/?loader", "routes/index");
547+
let text = await response.text();
548+
expect(text).toBe('{"message":"Unexpected Server Error"}');
549+
expect(errorLogs[0][0]).toEqual("App Specific Error Logging:");
550+
expect(errorLogs[1][0]).toEqual(
551+
" Request: GET test://test/?loader=&_data=routes%2Findex"
552+
);
553+
expect(errorLogs[2][0]).toEqual(" Error: Loader Error");
554+
expect(errorLogs[3][0]).toMatch(" at ");
555+
expect(errorLogs.length).toBe(4);
556+
});
557+
558+
test("returns deferred data without errors", async () => {
559+
let response = await fixture.requestData("/defer", "routes/defer");
560+
let text = await response.text();
561+
expect(text).toMatch("RESOLVED");
562+
expect(text).not.toMatch("REJECTED");
563+
expect(text).not.toMatch(/stack/i);
564+
});
565+
566+
test("sanitizes loader errors in deferred data requests", async () => {
567+
let response = await fixture.requestData("/defer?loader", "routes/defer");
568+
let text = await response.text();
569+
expect(text).toBe(
570+
'{"lazy":"__deferred_promise:lazy"}\n\n' +
571+
'error:{"lazy":{"message":"Unexpected Server Error"}}\n\n'
572+
);
573+
// defer errors are not logged to the server console since the request
574+
// has "succeeded"
575+
expect(errorLogs.length).toBe(0);
576+
});
577+
578+
test("sanitizes loader errors in resource requests", async () => {
579+
let response = await fixture.requestData(
580+
"/resource?loader",
581+
"routes/resource"
582+
);
583+
let text = await response.text();
584+
expect(text).toBe('{"message":"Unexpected Server Error"}');
585+
expect(errorLogs[0][0]).toEqual("App Specific Error Logging:");
586+
expect(errorLogs[1][0]).toEqual(
587+
" Request: GET test://test/resource?loader=&_data=routes%2Fresource"
588+
);
589+
expect(errorLogs[2][0]).toEqual(" Error: Loader Error");
590+
expect(errorLogs[3][0]).toMatch(" at ");
591+
expect(errorLogs.length).toBe(4);
592+
});
593+
594+
test("sanitizes mismatched route errors in data requests", async () => {
595+
let response = await fixture.requestData("/", "not-a-route");
596+
let text = await response.text();
597+
expect(text).toBe('{"message":"Unexpected Server Error"}');
598+
expect(errorLogs[0][0]).toEqual("App Specific Error Logging:");
599+
expect(errorLogs[1][0]).toEqual(
600+
" Request: GET test://test/?_data=not-a-route"
601+
);
602+
expect(errorLogs[2][0]).toEqual(
603+
' Error: Route "not-a-route" does not match URL "/"'
604+
);
605+
expect(errorLogs[3][0]).toMatch(" at ");
606+
expect(errorLogs.length).toBe(4);
607+
});
608+
});
420609
});

0 commit comments

Comments
 (0)