entry.server.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. /**
  2. * By default, Remix will handle generating the HTTP Response for you.
  3. * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
  4. * For more information, see https://remix.run/file-conventions/entry.server
  5. */
  6. import { PassThrough } from "node:stream";
  7. import type { AppLoadContext, EntryContext } from "@remix-run/node";
  8. import { createReadableStreamFromReadable } from "@remix-run/node";
  9. import { RemixServer } from "@remix-run/react";
  10. import { isbot } from "isbot";
  11. import { renderToPipeableStream } from "react-dom/server";
  12. const ABORT_DELAY = 5_000;
  13. export default function handleRequest(
  14. request: Request,
  15. responseStatusCode: number,
  16. responseHeaders: Headers,
  17. remixContext: EntryContext,
  18. // This is ignored so we can keep it in the template for visibility. Feel
  19. // free to delete this parameter in your app if you're not using it!
  20. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  21. loadContext: AppLoadContext
  22. ) {
  23. return isbot(request.headers.get("user-agent") || "")
  24. ? handleBotRequest(
  25. request,
  26. responseStatusCode,
  27. responseHeaders,
  28. remixContext
  29. )
  30. : handleBrowserRequest(
  31. request,
  32. responseStatusCode,
  33. responseHeaders,
  34. remixContext
  35. );
  36. }
  37. function handleBotRequest(
  38. request: Request,
  39. responseStatusCode: number,
  40. responseHeaders: Headers,
  41. remixContext: EntryContext
  42. ) {
  43. return new Promise((resolve, reject) => {
  44. let shellRendered = false;
  45. const { pipe, abort } = renderToPipeableStream(
  46. <RemixServer
  47. context={remixContext}
  48. url={request.url}
  49. abortDelay={ABORT_DELAY}
  50. />,
  51. {
  52. onAllReady() {
  53. shellRendered = true;
  54. const body = new PassThrough();
  55. const stream = createReadableStreamFromReadable(body);
  56. responseHeaders.set("Content-Type", "text/html");
  57. resolve(
  58. new Response(stream, {
  59. headers: responseHeaders,
  60. status: responseStatusCode,
  61. })
  62. );
  63. pipe(body);
  64. },
  65. onShellError(error: unknown) {
  66. reject(error);
  67. },
  68. onError(error: unknown) {
  69. responseStatusCode = 500;
  70. // Log streaming rendering errors from inside the shell. Don't log
  71. // errors encountered during initial shell rendering since they'll
  72. // reject and get logged in handleDocumentRequest.
  73. if (shellRendered) {
  74. console.error(error);
  75. }
  76. },
  77. }
  78. );
  79. setTimeout(abort, ABORT_DELAY);
  80. });
  81. }
  82. function handleBrowserRequest(
  83. request: Request,
  84. responseStatusCode: number,
  85. responseHeaders: Headers,
  86. remixContext: EntryContext
  87. ) {
  88. return new Promise((resolve, reject) => {
  89. let shellRendered = false;
  90. const { pipe, abort } = renderToPipeableStream(
  91. <RemixServer
  92. context={remixContext}
  93. url={request.url}
  94. abortDelay={ABORT_DELAY}
  95. />,
  96. {
  97. onShellReady() {
  98. shellRendered = true;
  99. const body = new PassThrough();
  100. const stream = createReadableStreamFromReadable(body);
  101. responseHeaders.set("Content-Type", "text/html");
  102. resolve(
  103. new Response(stream, {
  104. headers: responseHeaders,
  105. status: responseStatusCode,
  106. })
  107. );
  108. pipe(body);
  109. },
  110. onShellError(error: unknown) {
  111. reject(error);
  112. },
  113. onError(error: unknown) {
  114. responseStatusCode = 500;
  115. // Log streaming rendering errors from inside the shell. Don't log
  116. // errors encountered during initial shell rendering since they'll
  117. // reject and get logged in handleDocumentRequest.
  118. if (shellRendered) {
  119. console.error(error);
  120. }
  121. },
  122. }
  123. );
  124. setTimeout(abort, ABORT_DELAY);
  125. });
  126. }