123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- /**
- * By default, Remix will handle generating the HTTP Response for you.
- * 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` ✨
- * For more information, see https://remix.run/file-conventions/entry.server
- */
- import { PassThrough } from "node:stream";
- import type { AppLoadContext, EntryContext } from "@remix-run/node";
- import { createReadableStreamFromReadable } from "@remix-run/node";
- import { RemixServer } from "@remix-run/react";
- import { isbot } from "isbot";
- import { renderToPipeableStream } from "react-dom/server";
- const ABORT_DELAY = 5_000;
- export default function handleRequest(
- request: Request,
- responseStatusCode: number,
- responseHeaders: Headers,
- remixContext: EntryContext,
- // This is ignored so we can keep it in the template for visibility. Feel
- // free to delete this parameter in your app if you're not using it!
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- loadContext: AppLoadContext
- ) {
- return isbot(request.headers.get("user-agent") || "")
- ? handleBotRequest(
- request,
- responseStatusCode,
- responseHeaders,
- remixContext
- )
- : handleBrowserRequest(
- request,
- responseStatusCode,
- responseHeaders,
- remixContext
- );
- }
- function handleBotRequest(
- request: Request,
- responseStatusCode: number,
- responseHeaders: Headers,
- remixContext: EntryContext
- ) {
- return new Promise((resolve, reject) => {
- let shellRendered = false;
- const { pipe, abort } = renderToPipeableStream(
- <RemixServer
- context={remixContext}
- url={request.url}
- abortDelay={ABORT_DELAY}
- />,
- {
- onAllReady() {
- shellRendered = true;
- const body = new PassThrough();
- const stream = createReadableStreamFromReadable(body);
- responseHeaders.set("Content-Type", "text/html");
- resolve(
- new Response(stream, {
- headers: responseHeaders,
- status: responseStatusCode,
- })
- );
- pipe(body);
- },
- onShellError(error: unknown) {
- reject(error);
- },
- onError(error: unknown) {
- responseStatusCode = 500;
- // Log streaming rendering errors from inside the shell. Don't log
- // errors encountered during initial shell rendering since they'll
- // reject and get logged in handleDocumentRequest.
- if (shellRendered) {
- console.error(error);
- }
- },
- }
- );
- setTimeout(abort, ABORT_DELAY);
- });
- }
- function handleBrowserRequest(
- request: Request,
- responseStatusCode: number,
- responseHeaders: Headers,
- remixContext: EntryContext
- ) {
- return new Promise((resolve, reject) => {
- let shellRendered = false;
- const { pipe, abort } = renderToPipeableStream(
- <RemixServer
- context={remixContext}
- url={request.url}
- abortDelay={ABORT_DELAY}
- />,
- {
- onShellReady() {
- shellRendered = true;
- const body = new PassThrough();
- const stream = createReadableStreamFromReadable(body);
- responseHeaders.set("Content-Type", "text/html");
- resolve(
- new Response(stream, {
- headers: responseHeaders,
- status: responseStatusCode,
- })
- );
- pipe(body);
- },
- onShellError(error: unknown) {
- reject(error);
- },
- onError(error: unknown) {
- responseStatusCode = 500;
- // Log streaming rendering errors from inside the shell. Don't log
- // errors encountered during initial shell rendering since they'll
- // reject and get logged in handleDocumentRequest.
- if (shellRendered) {
- console.error(error);
- }
- },
- }
- );
- setTimeout(abort, ABORT_DELAY);
- });
- }
|