diff --git a/app/entry.client.tsx b/app/entry.client.tsx index f95b51d4..62917e70 100644 --- a/app/entry.client.tsx +++ b/app/entry.client.tsx @@ -3,5 +3,5 @@ import { startTransition } from 'react'; import { hydrateRoot } from 'react-dom/client'; startTransition(() => { - hydrateRoot(document, ); + hydrateRoot(document.getElementById('root')!, ); }); diff --git a/app/entry.server.tsx b/app/entry.server.tsx index de199824..4baf0700 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -2,6 +2,9 @@ import type { AppLoadContext, EntryContext } from '@remix-run/cloudflare'; import { RemixServer } from '@remix-run/react'; import { isbot } from 'isbot'; import { renderToReadableStream } from 'react-dom/server'; +import { renderHeadToString } from 'remix-island'; +import { Head } from './root'; +import { themeStore } from '~/lib/stores/theme'; export default async function handleRequest( request: Request, @@ -10,7 +13,7 @@ export default async function handleRequest( remixContext: EntryContext, _loadContext: AppLoadContext, ) { - const body = await renderToReadableStream(, { + const readable = await renderToReadableStream(, { signal: request.signal, onError(error: unknown) { console.error(error); @@ -18,8 +21,49 @@ export default async function handleRequest( }, }); + const body = new ReadableStream({ + start(controller) { + const head = renderHeadToString({ request, remixContext, Head }); + + controller.enqueue( + new Uint8Array( + new TextEncoder().encode( + `${head}
`, + ), + ), + ); + + const reader = readable.getReader(); + + function read() { + reader + .read() + .then(({ done, value }) => { + if (done) { + controller.enqueue(new Uint8Array(new TextEncoder().encode(`
`))); + controller.close(); + + return; + } + + controller.enqueue(value); + read(); + }) + .catch((error) => { + controller.error(error); + readable.cancel(); + }); + } + read(); + }, + + cancel() { + readable.cancel(); + }, + }); + if (isbot(request.headers.get('user-agent') || '')) { - await body.allReady; + await readable.allReady; } responseHeaders.set('Content-Type', 'text/html'); diff --git a/app/root.tsx b/app/root.tsx index c122bf6f..31eb387e 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -4,6 +4,8 @@ import { Links, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/reac import tailwindReset from '@unocss/reset/tailwind-compat.css?url'; import { themeStore } from './lib/stores/theme'; import { stripIndents } from './utils/stripIndent'; +import { createHead } from 'remix-island'; +import { useEffect } from 'react'; import reactToastifyStyles from 'react-toastify/dist/ReactToastify.css?url'; import globalStyles from './styles/index.scss?url'; @@ -50,24 +52,29 @@ const inlineThemeCode = stripIndents` } `; +export const Head = createHead(() => ( + <> + + + + +