fix(browser-extensions): don't render directly in body

This commit is contained in:
Sam Denty
2024-10-04 09:31:25 +02:00
committed by Dominic Elm
parent b17d6a58f5
commit 50885b065d
5 changed files with 87 additions and 17 deletions

View File

@@ -3,5 +3,5 @@ import { startTransition } from 'react';
import { hydrateRoot } from 'react-dom/client';
startTransition(() => {
hydrateRoot(document, <RemixBrowser />);
hydrateRoot(document.getElementById('root')!, <RemixBrowser />);
});

View File

@@ -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(<RemixServer context={remixContext} url={request.url} />, {
const readable = await renderToReadableStream(<RemixServer context={remixContext} url={request.url} />, {
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(
`<!DOCTYPE html><html lang="en" data-theme="${themeStore.value}"><head>${head}</head><body><div id="root" class="w-full h-full">`,
),
),
);
const reader = readable.getReader();
function read() {
reader
.read()
.then(({ done, value }) => {
if (done) {
controller.enqueue(new Uint8Array(new TextEncoder().encode(`</div></body></html>`)));
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');

View File

@@ -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(() => (
<>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
<script dangerouslySetInnerHTML={{ __html: inlineThemeCode }} />
</>
));
export function Layout({ children }: { children: React.ReactNode }) {
const theme = useStore(themeStore);
useEffect(() => {
document.querySelector('html')?.setAttribute('data-theme', theme);
}, [theme]);
return (
<html lang="en" data-theme={theme}>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
<script dangerouslySetInnerHTML={{ __html: inlineThemeCode }} />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
<>
{children}
<ScrollRestoration />
<Scripts />
</>
);
}