|
|
|
|
|
|
|
|
|
|
|
|
|
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, |
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
if (shellRendered) { |
|
console.error(error); |
|
} |
|
}, |
|
} |
|
); |
|
|
|
setTimeout(abort, ABORT_DELAY); |
|
}); |
|
} |
|
|