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(() => (
+ <>
+
+
+
+
+
+ >
+));
+
export function Layout({ children }: { children: React.ReactNode }) {
const theme = useStore(themeStore);
+ useEffect(() => {
+ document.querySelector('html')?.setAttribute('data-theme', theme);
+ }, [theme]);
+
return (
-
-
-
-
-
-
-
-
-
- {children}
-
-
-
-
+ <>
+ {children}
+
+
+ >
);
}
diff --git a/package.json b/package.json
index 10fd94a6..4f0061f1 100644
--- a/package.json
+++ b/package.json
@@ -73,6 +73,7 @@
"remark-gfm": "^4.0.0",
"remix-utils": "^7.6.0",
"shiki": "^1.9.1",
+ "remix-island": "^0.2.0",
"unist-util-visit": "^5.0.0"
},
"devDependencies": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2c593c0b..0896b714 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -155,6 +155,9 @@ importers:
remark-gfm:
specifier: ^4.0.0
version: 4.0.0
+ remix-island:
+ specifier: ^0.2.0
+ version: 0.2.0(@remix-run/react@2.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.2))(@remix-run/server-runtime@2.10.2(typescript@5.5.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
remix-utils:
specifier: ^7.6.0
version: 7.6.0(@remix-run/cloudflare@2.10.2(@cloudflare/workers-types@4.20240620.0)(typescript@5.5.2))(@remix-run/node@2.10.2(typescript@5.5.2))(@remix-run/react@2.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.2))(@remix-run/router@1.17.1)(react@18.3.1)(zod@3.23.8)
@@ -4331,6 +4334,14 @@ packages:
remark-stringify@11.0.0:
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
+ remix-island@0.2.0:
+ resolution: {integrity: sha512-NujWtmulgupxNOMiWKAj8lg56eYsy09aV/2pML8rov8N8LmY1UnSml4XYad+KHLy/pgZ1D9UxAmjI6GBJydTUg==}
+ peerDependencies:
+ '@remix-run/react': '>= 1'
+ '@remix-run/server-runtime': '>= 1'
+ react: '>= 16.8'
+ react-dom: '>= 16.8'
+
remix-utils@7.6.0:
resolution: {integrity: sha512-BPhCUEy+nwrhDDDg2v3+LFSszV6tluMbeSkbffj2o4tqZxt5Kn69Y9sNpGxYLAj8gjqeYDuxjv55of+gYnnykA==}
engines: {node: '>=18.0.0'}
@@ -10133,6 +10144,13 @@ snapshots:
mdast-util-to-markdown: 2.1.0
unified: 11.0.5
+ remix-island@0.2.0(@remix-run/react@2.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.2))(@remix-run/server-runtime@2.10.2(typescript@5.5.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ dependencies:
+ '@remix-run/react': 2.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.2)
+ '@remix-run/server-runtime': 2.10.2(typescript@5.5.2)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
remix-utils@7.6.0(@remix-run/cloudflare@2.10.2(@cloudflare/workers-types@4.20240620.0)(typescript@5.5.2))(@remix-run/node@2.10.2(typescript@5.5.2))(@remix-run/react@2.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.2))(@remix-run/router@1.17.1)(react@18.3.1)(zod@3.23.8):
dependencies:
type-fest: 4.21.0